From 1a5e00c15aecee8474118a3639b0927986dfb0f2 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 17 Jan 2022 13:26:53 +0000 Subject: [PATCH 01/24] Bump version to 7.3.1 Bump version to 7.3.1 for next release. --- GitVersionConfig.yaml | 2 +- src/Polly/Polly.csproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GitVersionConfig.yaml b/GitVersionConfig.yaml index 0105503e461..0bbd2edff45 100644 --- a/GitVersionConfig.yaml +++ b/GitVersionConfig.yaml @@ -1 +1 @@ -next-version: 7.3.0 +next-version: 7.3.1 diff --git a/src/Polly/Polly.csproj b/src/Polly/Polly.csproj index 6bfe96ca0c8..a56a6e49965 100644 --- a/src/Polly/Polly.csproj +++ b/src/Polly/Polly.csproj @@ -2,11 +2,11 @@ netstandard1.1;netstandard2.0;net461;net472 - 7.3.0 + 7.3.1 7.0.0.0 - 7.3.0.0 - 7.3.0.0 - 7.3.0 + 7.3.1.0 + 7.3.1.0 + 7.3.1 App vNext Copyright (c) $([System.DateTime]::Now.ToString(yyyy)), App vNext Polly is a library that allows developers to express resilience and transient fault handling policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. From dfa90407a88fe350c3775c4d396c7d2d43aae9d7 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 17 Jan 2022 16:56:17 +0000 Subject: [PATCH 02/24] Change version to 7.2.4 Change version to 7.2.4 for next release. --- GitVersionConfig.yaml | 2 +- src/Polly/Polly.csproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GitVersionConfig.yaml b/GitVersionConfig.yaml index 0bbd2edff45..1b138331757 100644 --- a/GitVersionConfig.yaml +++ b/GitVersionConfig.yaml @@ -1 +1 @@ -next-version: 7.3.1 +next-version: 7.2.4 diff --git a/src/Polly/Polly.csproj b/src/Polly/Polly.csproj index a56a6e49965..12e76e76815 100644 --- a/src/Polly/Polly.csproj +++ b/src/Polly/Polly.csproj @@ -2,11 +2,11 @@ netstandard1.1;netstandard2.0;net461;net472 - 7.3.1 + 7.2.4 7.0.0.0 - 7.3.1.0 - 7.3.1.0 - 7.3.1 + 7.2.4.0 + 7.2.4.0 + 7.2.4 App vNext Copyright (c) $([System.DateTime]::Now.ToString(yyyy)), App vNext Polly is a library that allows developers to express resilience and transient fault handling policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. From ac01478ec86e13aa8832773eb676f6a304a7e92b Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Mon, 17 Jan 2022 17:19:19 +0000 Subject: [PATCH 03/24] Add RateLimit benchmarks (#910) * Add RateLimit benchmarks Add benchmarks for the rate-limit policy. * Change NuGet baseline The latest release is 7.2.3, not 7.3.0. --- src/Polly.Benchmarks/PollyConfig.cs | 2 +- src/Polly.Benchmarks/RateLimit.cs | 37 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/Polly.Benchmarks/RateLimit.cs diff --git a/src/Polly.Benchmarks/PollyConfig.cs b/src/Polly.Benchmarks/PollyConfig.cs index d490ae4d09c..cad9e4148df 100644 --- a/src/Polly.Benchmarks/PollyConfig.cs +++ b/src/Polly.Benchmarks/PollyConfig.cs @@ -28,7 +28,7 @@ private static Job PollyJob(Job job, bool useNuGet) if (useNuGet) { - result = result.WithNuGet("Polly", "7.2.1"); + result = result.WithNuGet("Polly", "7.2.3"); } return result; diff --git a/src/Polly.Benchmarks/RateLimit.cs b/src/Polly.Benchmarks/RateLimit.cs new file mode 100644 index 00000000000..e883a5e9ba5 --- /dev/null +++ b/src/Polly.Benchmarks/RateLimit.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Polly.Benchmarks +{ + [Config(typeof(PollyConfig))] + public class RateLimit + { + private static readonly Policy SyncPolicy = Policy.RateLimit(20, TimeSpan.FromSeconds(1), int.MaxValue); + private static readonly AsyncPolicy AsyncPolicy = Policy.RateLimitAsync(20, TimeSpan.FromSeconds(1), int.MaxValue); + + [Benchmark] + public void RateLimit_Synchronous_Succeeds() + { + SyncPolicy.Execute(() => Workloads.Action()); + } + + [Benchmark] + public async Task RateLimit_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } + + [Benchmark] + public int RateLimit_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } + + [Benchmark] + public async Task RateLimit_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } + } +} From 9d13eac73a7a886a4d2adef89841f8ff2c7d81dd Mon Sep 17 00:00:00 2001 From: Georgy Levchenko <55885862+FoxTes@users.noreply.github.com> Date: Thu, 3 Feb 2022 17:03:58 +0400 Subject: [PATCH 04/24] Fixed an incorrect exception argument (#915) Co-authored-by: Georgy Levchenko --- src/Polly/Caching/AsyncCacheSyntax.cs | 2 +- src/Polly/Caching/AsyncCacheTResultSyntax.cs | 2 +- src/Polly/Caching/CacheSyntax.cs | 2 +- src/Polly/Caching/CacheTResultSyntax.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Polly/Caching/AsyncCacheSyntax.cs b/src/Polly/Caching/AsyncCacheSyntax.cs index 5a8529d6695..2579215b4b9 100644 --- a/src/Polly/Caching/AsyncCacheSyntax.cs +++ b/src/Polly/Caching/AsyncCacheSyntax.cs @@ -319,7 +319,7 @@ public static AsyncCachePolicy CacheAsync( if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); diff --git a/src/Polly/Caching/AsyncCacheTResultSyntax.cs b/src/Polly/Caching/AsyncCacheTResultSyntax.cs index a38f3616935..89e32f88c66 100644 --- a/src/Polly/Caching/AsyncCacheTResultSyntax.cs +++ b/src/Polly/Caching/AsyncCacheTResultSyntax.cs @@ -814,7 +814,7 @@ public static AsyncCachePolicy CacheAsync( if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); diff --git a/src/Polly/Caching/CacheSyntax.cs b/src/Polly/Caching/CacheSyntax.cs index 2d0551b4f73..612dba40f34 100644 --- a/src/Polly/Caching/CacheSyntax.cs +++ b/src/Polly/Caching/CacheSyntax.cs @@ -331,7 +331,7 @@ public static CachePolicy Cache( if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); diff --git a/src/Polly/Caching/CacheTResultSyntax.cs b/src/Polly/Caching/CacheTResultSyntax.cs index 2896d6c8a45..c291902bfab 100644 --- a/src/Polly/Caching/CacheTResultSyntax.cs +++ b/src/Polly/Caching/CacheTResultSyntax.cs @@ -819,7 +819,7 @@ public static CachePolicy Cache( if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); From 9d7d8bc6be8eaed8c685908f8e00e55190437e9c Mon Sep 17 00:00:00 2001 From: Yadel Lopez <63487874+ylr-research@users.noreply.github.com> Date: Sun, 13 Feb 2022 05:47:36 -0500 Subject: [PATCH 05/24] Upgrade FluentAssertions (#918) * Upgrade FluentAssertions package * Fix broken test * Async all the things Await all usages of Awaiting(). Co-authored-by: martincostello --- .../Bulkhead/BulkheadAsyncSpecs.cs | 6 +- .../Bulkhead/BulkheadTResultAsyncSpecs.cs | 6 +- src/Polly.Specs/Caching/CacheAsyncSpecs.cs | 8 +- .../Caching/CacheTResultAsyncSpecs.cs | 8 +- src/Polly.Specs/Caching/RelativeTtlSpecs.cs | 4 +- .../AdvancedCircuitBreakerAsyncSpecs.cs | 1120 +++++++++-------- .../CircuitBreakerAsyncSpecs.cs | 528 ++++---- .../CircuitBreakerTResultAsyncSpecs.cs | 150 +-- src/Polly.Specs/Custom/CustomAsyncSpecs.cs | 20 +- .../Custom/CustomTResultAsyncSpecs.cs | 6 +- .../Fallback/FallbackAsyncSpecs.cs | 159 +-- .../Fallback/FallbackTResultAsyncSpecs.cs | 22 +- .../IAsyncPolicyExtensionsSpecs.cs | 2 +- src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs | 13 +- src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs | 12 +- src/Polly.Specs/PolicyAsyncSpecs.cs | 56 +- src/Polly.Specs/PolicyTResultAsyncSpecs.cs | 30 +- src/Polly.Specs/Polly.Specs.csproj | 2 +- .../Registry/ReadOnlyPolicyRegistrySpecs.cs | 5 +- src/Polly.Specs/Retry/RetryAsyncSpecs.cs | 216 ++-- .../Retry/RetryForeverAsyncSpecs.cs | 140 +-- src/Polly.Specs/Retry/RetryForeverSpecs.cs | 21 +- src/Polly.Specs/Retry/RetrySpecs.cs | 2 +- .../Retry/RetryTResultSpecsAsync.cs | 64 +- .../Retry/WaitAndRetryAsyncSpecs.cs | 222 ++-- .../Retry/WaitAndRetryForeverAsyncSpecs.cs | 140 +-- .../Retry/WaitAndRetryForeverSpecs.cs | 2 +- src/Polly.Specs/Retry/WaitAndRetrySpecs.cs | 28 +- src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs | 164 ++- src/Polly.Specs/Timeout/TimeoutSpecs.cs | 4 +- .../Timeout/TimeoutTResultAsyncSpecs.cs | 140 +-- .../Timeout/TimeoutTResultSpecs.cs | 4 +- src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs | 20 +- src/Polly/Polly.csproj | 2 +- 34 files changed, 1664 insertions(+), 1662 deletions(-) diff --git a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs index da37e103487..e25222f4a9e 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs @@ -52,7 +52,7 @@ public void Should_throw_when_onBulkheadRejected_is_null() #region onBulkheadRejected delegate [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() + public async Task Should_call_onBulkheadRejected_with_passed_context() { string operationKey = "SomeKey"; Context contextPassedToExecute = new Context(operationKey); @@ -65,11 +65,11 @@ public void Should_call_onBulkheadRejected_with_passed_context() TaskCompletionSource tcs = new TaskCompletionSource(); using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) { - Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; }); }); + _ = Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; }); }); Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - bulkhead.Awaiting(b => b.ExecuteAsync(_ => TaskHelper.EmptyTask, contextPassedToExecute)).Should().Throw(); + await bulkhead.Awaiting(b => b.ExecuteAsync(_ => TaskHelper.EmptyTask, contextPassedToExecute)).Should().ThrowAsync(); cancellationSource.Cancel(); tcs.SetCanceled(); diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs index 3bf4862a945..f5ca561eaab 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs @@ -53,7 +53,7 @@ public void Should_throw_when_onBulkheadRejected_is_null() #region onBulkheadRejected delegate [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() + public async Task Should_call_onBulkheadRejected_with_passed_context() { string operationKey = "SomeKey"; Context contextPassedToExecute = new Context(operationKey); @@ -66,7 +66,7 @@ public void Should_call_onBulkheadRejected_with_passed_context() TaskCompletionSource tcs = new TaskCompletionSource(); using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) { - Task.Run(() => { + _ = Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; @@ -76,7 +76,7 @@ public void Should_call_onBulkheadRejected_with_passed_context() Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - bulkhead.Awaiting(b => b.ExecuteAsync(_ => Task.FromResult(1), contextPassedToExecute)).Should().Throw(); + await bulkhead.Awaiting(b => b.ExecuteAsync(_ => Task.FromResult(1), contextPassedToExecute)).Should().ThrowAsync(); cancellationSource.Cancel(); tcs.SetCanceled(); diff --git a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs index fd8884db1e1..83fdb8091a1 100644 --- a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs @@ -475,8 +475,8 @@ public async Task Should_honour_cancellation_even_if_prior_execution_has_cached( tokenSource.Cancel(); - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); delegateInvocations.Should().Be(1); } @@ -499,8 +499,8 @@ public async Task Should_honour_cancellation_during_delegate_execution_and_not_p return valueToReturn; }; - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit.Should().BeFalse(); diff --git a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs index e58fb6fe45b..d7f038e65c4 100644 --- a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs @@ -459,8 +459,8 @@ public async Task Should_honour_cancellation_even_if_prior_execution_has_cached( tokenSource.Cancel(); - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); delegateInvocations.Should().Be(1); } @@ -483,8 +483,8 @@ public async Task Should_honour_cancellation_during_delegate_execution_and_not_p return valueToReturn; }; - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit.Should().BeFalse(); diff --git a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs index c2f4731ff75..b3edd1da1c4 100644 --- a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs +++ b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs @@ -40,7 +40,7 @@ public void Should_return_configured_timespan() RelativeTtl ttlStrategy = new RelativeTtl(ttl); Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().BeCloseTo(ttl); + retrieved.Timespan.Should().BeCloseTo(ttl, TimeSpan.Zero); retrieved.SlidingExpiration.Should().BeFalse(); } @@ -56,7 +56,7 @@ public void Should_return_configured_timespan_from_time_requested() SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(delay); Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().BeCloseTo(ttl); + retrieved.Timespan.Should().BeCloseTo(ttl, TimeSpan.Zero); retrieved.SlidingExpiration.Should().BeFalse(); } } diff --git a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs index f1817c01715..7bef1149303 100644 --- a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs @@ -19,14 +19,14 @@ public class AdvancedCircuitBreakerAsyncSpecs : IDisposable #region Configuration tests [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() { var breaker = Policy .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] @@ -170,7 +170,7 @@ public void Should_initialise_to_closed_state() // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. [Fact] - public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() + public async Task Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -185,28 +185,28 @@ public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_i ); // Three of three actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Failure threshold exceeded, but throughput threshold not yet. // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice } [Fact] - public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() + public async Task Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -222,20 +222,20 @@ public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_spec ); // Four of four actions in this test throw unhandled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } @@ -250,7 +250,7 @@ public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_spec // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() + public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -265,36 +265,36 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); delegateExecutedWhenBroken.Should().BeFalse(); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -311,16 +311,16 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later @@ -328,20 +328,20 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh // They are still placed within same timeslice. SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -356,33 +356,33 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Three of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -399,20 +399,20 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Three of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later @@ -420,16 +420,16 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh // They are still placed within same timeslice SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -444,33 +444,33 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Two of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -487,20 +487,20 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Two of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later @@ -508,16 +508,16 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh // They are still placed within same timeslice SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -534,29 +534,29 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput ); // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -573,29 +573,29 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput ); // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. SystemClock.UtcNow = () => time.Add(samplingDuration); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -614,32 +614,32 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. // Two actions at the start of the original timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Creates a new window right at the end of the original timeslice. SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. SystemClock.UtcNow = () => time.Add(samplingDuration); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -656,35 +656,35 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() + public async Task Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -699,22 +699,22 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_ ); // One of three actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice } [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() + public async Task Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -729,26 +729,26 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_ ); // One of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice } [Fact] - public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() + public async Task Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -766,8 +766,8 @@ public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failu // Executing a single invocation to ensure timeslice is created // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // The time is set to just at the end of the sampling duration ensuring @@ -775,16 +775,16 @@ public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failu SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); // Three of four actions in this test occur within the first timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Setting the time to just barely into the new timeslice @@ -793,13 +793,13 @@ public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failu // This failure opens the circuit, because it is the second failure of four calls // equalling the failure threshold. The minimum threshold within the defined // sampling duration is met, when using rolling windows. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() + public async Task Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -817,8 +817,8 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_fai // Executing a single invocation to ensure timeslice is created // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // The time is set to just at the end of the sampling duration ensuring @@ -826,12 +826,12 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_fai SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); // Two of three actions in this test occur within the first timeslice. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Setting the time to just barely into the new timeslice @@ -840,13 +840,13 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_fai // A third failure occurs just at the beginning of the new timeslice making // the number of failures above the failure threshold. However, the throughput is // below the minimum threshold as to open the circuit. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() + public async Task Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -865,31 +865,31 @@ public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_a // Executing a single invocation to ensure timeslice is created // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Setting the time to the second window in the rolling metrics SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); // Three actions occur in the second window of the first timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Setting the time to just barely into the new timeslice SystemClock.UtcNow = () => time.Add(samplingDuration); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); } @@ -904,7 +904,7 @@ public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_a // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -919,34 +919,34 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -961,33 +961,33 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Three of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1002,33 +1002,33 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Two of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1045,29 +1045,29 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput ); // Four of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1084,29 +1084,29 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput ); // Two of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. SystemClock.UtcNow = () => time.Add(samplingDuration); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1123,35 +1123,35 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() + public async Task Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1166,22 +1166,22 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_ ); // One of three actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice } [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() + public async Task Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1196,26 +1196,26 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_ ); // One of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice } [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() + public async Task Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1233,8 +1233,8 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_f // Executing a single invocation to ensure timeslice is created // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // The time is set to just at the end of the sampling duration ensuring @@ -1242,16 +1242,16 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_f SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); // Three of four actions in this test occur within the first timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Setting the time to just barely into the new timeslice @@ -1259,8 +1259,8 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_f // This failure does not open the circuit, because a new duration should have // started and with such low sampling duration, windows should not be used. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } @@ -1271,7 +1271,7 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_f #region Circuit-breaker open->half-open->open/closed tests [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1288,20 +1288,20 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1309,12 +1309,12 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with // duration has passed, circuit now half open breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1332,16 +1332,16 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later @@ -1350,8 +1350,8 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with var anotherWindowDuration = samplingDuration.Seconds / 2d + 1; SystemClock.UtcNow = () => time.AddSeconds(anotherWindowDuration); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1364,12 +1364,12 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with // duration has passed, circuit now half open breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1386,20 +1386,20 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1409,11 +1409,11 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if breaker.CircuitState.Should().Be(CircuitState.HalfOpen); // first call after duration raises an exception, so circuit should open again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } @@ -1435,20 +1435,20 @@ public async Task Should_reset_circuit_after_the_specified_duration_has_passed_i ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1463,7 +1463,7 @@ public async Task Should_reset_circuit_after_the_specified_duration_has_passed_i } [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1478,10 +1478,10 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ durationOfBreak: durationOfBreak ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exception raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1501,7 +1501,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ } [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1516,10 +1516,10 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ durationOfBreak: durationOfBreak ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exception raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1547,7 +1547,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ } [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1562,10 +1562,10 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ durationOfBreak: durationOfBreak ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exceptions raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1587,9 +1587,9 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(async () => { - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. @@ -1602,7 +1602,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ await TaskHelper.EmptyTask; firstExecutionActive = false; - })).Should().NotThrow(); + })).Should().NotThrowAsync(); }, TaskCreationOptions.LongRunning); // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. @@ -1653,7 +1653,7 @@ await breaker.ExecuteAsync(async () => } [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1668,10 +1668,10 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ durationOfBreak: durationOfBreak ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exception raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1694,9 +1694,9 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(async () => { - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. @@ -1708,7 +1708,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); await TaskHelper.EmptyTask; firstExecutionActive = false; - })).Should().NotThrow(); + })).Should().NotThrowAsync(); }, TaskCreationOptions.LongRunning); // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. @@ -1765,7 +1765,7 @@ await breaker.ExecuteAsync(async () => #region Isolate and reset tests [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() + public async Task Should_open_circuit_and_block_calls_if_manual_override_open() { var breaker = Policy .Handle() @@ -1778,15 +1778,15 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Isolated); breaker.LastException.Should().BeOfType(); delegateExecutedWhenBroken.Should().BeFalse(); } [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1804,13 +1804,13 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope breaker.CircuitState.Should().Be(CircuitState.Isolated); bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); delegateExecutedWhenBroken.Should().BeFalse(); } [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() + public async Task Should_close_circuit_again_on_reset_after_manual_override() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1823,17 +1823,17 @@ public void Should_close_circuit_again_on_reset_after_manual_override() breaker.Isolate(); breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); breaker.Reset(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); } [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1850,28 +1850,28 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // reset circuit, with no time having passed breaker.Reset(); SystemClock.UtcNow().Should().Be(time); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); } #endregion @@ -1894,7 +1894,7 @@ public void Should_not_call_onreset_on_initialise() } [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() + public async Task Should_call_onbreak_when_breaking_circuit_automatically() { bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; @@ -1915,22 +1915,22 @@ public void Should_call_onbreak_when_breaking_circuit_automatically() ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().BeTrue(); @@ -1956,7 +1956,7 @@ public void Should_call_onbreak_when_breaking_circuit_manually() } [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; @@ -1977,39 +1977,39 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); // call through circuit when already broken - should not retrigger onBreak - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); } [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; @@ -2032,11 +2032,11 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - Task longRunningExecution = Task.Factory.StartNew(() => + Task longRunningExecution = Task.Factory.StartNew(async () => { breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { await TaskHelper.EmptyTask; @@ -2049,7 +2049,7 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub breaker.CircuitState.Should().Be(CircuitState.Open); throw new DivideByZeroException(); - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + })).Should().ThrowAsync(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. }, TaskCreationOptions.LongRunning); permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); @@ -2057,10 +2057,10 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub // Break circuit in the normal manner: onBreak() should be called once. breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -2103,22 +2103,22 @@ public async Task Should_call_onreset_when_automatically_closing_circuit_but_not ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -2187,25 +2187,25 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -2222,7 +2222,7 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal } [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { int onBreakCalled = 0; int onResetCalled = 0; @@ -2249,25 +2249,25 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -2279,7 +2279,7 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ } [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() + public async Task Should_call_onreset_when_manually_resetting_circuit() { int onBreakCalled = 0; int onResetCalled = 0; @@ -2299,22 +2299,22 @@ public void Should_call_onreset_when_manually_resetting_circuit() onBreakCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); onResetCalled.Should().Be(0); breaker.Reset(); onResetCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); } #region Tests of supplied parameters to onBreak delegate [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() + public async Task Should_call_onbreak_with_the_last_raised_exception() { Exception passedException = null; @@ -2333,29 +2333,29 @@ public void Should_call_onbreak_with_the_last_raised_exception() ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); passedException?.Should().BeOfType(); } [Fact] - public void Should_call_onbreak_with_a_state_of_closed() + public async Task Should_call_onbreak_with_a_state_of_closed() { CircuitState? transitionedState = null; @@ -2375,29 +2375,29 @@ public void Should_call_onbreak_with_a_state_of_closed() onHalfOpen: onHalfOpen ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); transitionedState?.Should().Be(CircuitState.Closed); } [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() + public async Task Should_call_onbreak_with_a_state_of_half_open() { List transitionedStates = new List(); @@ -2423,20 +2423,20 @@ public void Should_call_onbreak_with_a_state_of_half_open() ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -2446,18 +2446,18 @@ public void Should_call_onbreak_with_a_state_of_half_open() breaker.CircuitState.Should().Be(CircuitState.HalfOpen); // first call after duration raises an exception, so circuit should open again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); transitionedStates[0].Should().Be(CircuitState.Closed); transitionedStates[1].Should().Be(CircuitState.HalfOpen); } [Fact] - public void Should_call_onbreak_with_the_correct_timespan() + public async Task Should_call_onbreak_with_the_correct_timespan() { TimeSpan? passedBreakTimespan = null; @@ -2478,22 +2478,22 @@ public void Should_call_onbreak_with_the_correct_timespan() ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); passedBreakTimespan.Should().Be(durationOfBreak); @@ -2533,7 +2533,7 @@ public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() #region Tests that supplied context is passed to stage-change delegates [Fact] - public void Should_call_onbreak_with_the_passed_context() + public async Task Should_call_onbreak_with_the_passed_context() { IDictionary contextData = null; @@ -2555,21 +2555,21 @@ public void Should_call_onbreak_with_the_passed_context() ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync( + await breaker.Awaiting(x => x.RaiseExceptionAsync( new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().Throw(); + )).Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); contextData.Should() @@ -2602,20 +2602,20 @@ public async Task Should_call_onreset_with_the_passed_context() ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -2633,7 +2633,7 @@ public async Task Should_call_onreset_with_the_passed_context() } [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() { IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); @@ -2655,20 +2655,20 @@ public void Context_should_be_empty_if_execute_not_called_with_any_context_data( ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -2702,20 +2702,20 @@ public async Task Should_create_new_context_for_each_call_to_execute() ); // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); contextValue.Should().Be("original_value"); @@ -2754,7 +2754,7 @@ public void Should_initialise_LastException_to_null_on_creation() } [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + public async Task Should_set_LastException_on_handling_exception_even_when_not_breaking() { var breaker = Policy .Handle() @@ -2765,15 +2765,15 @@ public void Should_set_LastException_on_handling_exception_even_when_not_breakin durationOfBreak: TimeSpan.FromSeconds(30) ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); breaker.LastException.Should().BeOfType(); } [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() + public async Task Should_set_LastException_to_last_raised_exception_when_breaking() { var breaker = Policy .Handle() @@ -2784,19 +2784,19 @@ public void Should_set_LastException_to_last_raised_exception_when_breaking() durationOfBreak: TimeSpan.FromSeconds(30) ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); breaker.LastException.Should().BeOfType(); } [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() + public async Task Should_set_LastException_to_null_on_circuit_reset() { var breaker = Policy .Handle() @@ -2807,12 +2807,12 @@ public void Should_set_LastException_to_null_on_circuit_reset() durationOfBreak: TimeSpan.FromSeconds(30) ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); breaker.LastException.Should().BeOfType(); @@ -2827,7 +2827,7 @@ public void Should_set_LastException_to_null_on_circuit_reset() #region Cancellation support [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -2845,14 +2845,14 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca AttemptDuringWhichToCancel = null, }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -2873,15 +2873,15 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -2901,15 +2901,15 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -2929,15 +2929,15 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use ActionObservesCancellation = true }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -2956,30 +2956,30 @@ public void Should_report_faulting_from_faulting_action_execution_when_user_dele ActionObservesCancellation = false }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); // Circuit is now broken. CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); @@ -2997,15 +2997,15 @@ public void Should_report_cancellation_when_both_open_circuit_and_cancellation() ActionObservesCancellation = false }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex2 = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex2.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() { // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. @@ -3024,20 +3024,21 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act int attemptsInvoked = 0; - breaker.Awaiting(x => x.ExecuteAsync(async _ => + var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => { attemptsInvoked++; await TaskHelper.EmptyTask; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().ThrowAsync(); + + ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -3059,8 +3060,8 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().NotThrow(); + await breaker.Awaiting(action) + .Should().NotThrowAsync(); result.Should().BeTrue(); @@ -3068,7 +3069,7 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance } [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + public async Task Should_honour_and_report_cancellation_during_func_execution() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -3091,8 +3092,9 @@ public void Should_honour_and_report_cancellation_during_func_execution() }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(action) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); result.Should().Be(null); diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs index 375c71209cd..3116cf86673 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs @@ -19,14 +19,14 @@ public class CircuitBreakerAsyncSpecs : IDisposable #region Configuration tests [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() { var breaker = Policy .Handle() .CircuitBreakerAsync(1, TimeSpan.MaxValue); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] @@ -79,113 +79,113 @@ public void Should_initialise_to_closed_state() #region Circuit-breaker threshold-to-break tests [Fact] - public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() + public async Task Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() { var breaker = Policy .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(b => b.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + await breaker.Awaiting(b => b.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() + public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() { var breaker = Policy .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); delegateExecutedWhenBroken.Should().BeFalse(); } [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() + public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() { var breaker = Policy .Handle() .Or() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); breaker.CircuitState.Should().Be(CircuitState.Open); delegateExecutedWhenBroken.Should().BeFalse(); } [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() + public async Task Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() { var breaker = Policy .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() + public async Task Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() { var breaker = Policy .Handle() .Or() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } @@ -194,7 +194,7 @@ public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specif #region Circuit-breaker open->half-open->open/closed tests [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed() + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -205,29 +205,29 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed() .Handle() .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); // duration has passed, circuit now half open breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -238,17 +238,17 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if .Handle() .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); @@ -256,12 +256,11 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if // duration has passed, circuit now half open breaker.CircuitState.Should().Be(CircuitState.HalfOpen); // first call after duration raises an exception, so circuit should break again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] @@ -276,17 +275,17 @@ public async Task Should_reset_circuit_after_the_specified_duration_has_passed_i .Handle() .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); @@ -298,21 +297,21 @@ public async Task Should_reset_circuit_after_the_specified_duration_has_passed_i breaker.CircuitState.Should().Be(CircuitState.Closed); // circuit has been reset so should once again allow 2 exceptions to be raised before breaking - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); } [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -322,8 +321,8 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ .Handle() .CircuitBreakerAsync(1, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exception raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -343,7 +342,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ } [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -353,8 +352,8 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ .Handle() .CircuitBreakerAsync(1, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exception raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -382,7 +381,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ } [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -392,8 +391,8 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ .Handle() .CircuitBreakerAsync(1, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exception raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -415,9 +414,9 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(async () => { - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. @@ -430,7 +429,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ await TaskHelper.EmptyTask; firstExecutionActive = false; - })).Should().NotThrow(); + })).Should().NotThrowAsync(); }, TaskCreationOptions.LongRunning); // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. @@ -481,7 +480,7 @@ await breaker.ExecuteAsync(async () => } [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -491,8 +490,8 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ .Handle() .CircuitBreakerAsync(1, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // exception raised, circuit is now open. breaker.CircuitState.Should().Be(CircuitState.Open); @@ -515,9 +514,9 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(async () => { - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. @@ -529,7 +528,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); await TaskHelper.EmptyTask; firstExecutionActive = false; - })).Should().NotThrow(); + })).Should().NotThrowAsync(); }, TaskCreationOptions.LongRunning); // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. @@ -586,7 +585,7 @@ await breaker.ExecuteAsync(async () => #region Isolate and reset tests [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() + public async Task Should_open_circuit_and_block_calls_if_manual_override_open() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -604,8 +603,8 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Isolated); breaker.LastException.Should().BeOfType(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -613,7 +612,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() } [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -631,13 +630,13 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time.Add(durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Isolated); bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); delegateExecutedWhenBroken.Should().BeFalse(); } [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() + public async Task Should_close_circuit_again_on_reset_after_manual_override() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -651,16 +650,16 @@ public void Should_close_circuit_again_on_reset_after_manual_override() breaker.Isolate(); breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); breaker.Reset(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); } [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -671,24 +670,24 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi .Handle() .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // reset circuit, with no time having passed breaker.Reset(); SystemClock.UtcNow().Should().Be(time); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); } #endregion @@ -710,7 +709,7 @@ public void Should_not_call_onreset_on_initialise() } [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() + public async Task Should_call_onbreak_when_breaking_circuit_automatically() { bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; @@ -720,14 +719,14 @@ public void Should_call_onbreak_when_breaking_circuit_automatically() .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().BeFalse(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().BeTrue(); @@ -751,7 +750,7 @@ public void Should_call_onbreak_when_breaking_circuit_manually() } [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; @@ -761,28 +760,28 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); // call through circuit when already broken - should not retrigger onBreak - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); } [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; @@ -797,11 +796,11 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - Task longRunningExecution = Task.Factory.StartNew(() => + Task longRunningExecution = Task.Factory.StartNew(async () => { breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { await TaskHelper.EmptyTask; @@ -814,7 +813,7 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub breaker.CircuitState.Should().Be(CircuitState.Open); throw new DivideByZeroException(); - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + })).Should().ThrowAsync(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. }, TaskCreationOptions.LongRunning); permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); @@ -822,8 +821,8 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub // Break circuit in the normal manner: onBreak() should be called once. breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -842,7 +841,7 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub } [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() { int onBreakCalled = 0; int onResetCalled = 0; @@ -860,17 +859,17 @@ public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_ onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); onBreakCalled.Should().Be(1); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -882,13 +881,13 @@ public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_ onResetCalled.Should().Be(0); // first call after duration is successful, so circuit should reset - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().Be(1); } [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() + public async Task Should_not_call_onreset_on_successive_successful_calls() { Action onBreak = (_, _) => { }; bool onResetCalled = false; @@ -900,17 +899,17 @@ public void Should_not_call_onreset_on_successive_successful_calls() onResetCalled.Should().BeFalse(); - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().BeFalse(); - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().BeFalse(); } [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() { int onBreakCalled = 0; int onResetCalled = 0; @@ -930,17 +929,17 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); onBreakCalled.Should().Be(1); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -949,14 +948,14 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state // first call after duration is successful, so circuit should reset - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); onHalfOpenCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().Be(1); } [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { int onBreakCalled = 0; int onResetCalled = 0; @@ -976,17 +975,17 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); onBreakCalled.Should().Be(1); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -997,7 +996,7 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ } [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() + public async Task Should_call_onreset_when_manually_resetting_circuit() { int onBreakCalled = 0; int onResetCalled = 0; @@ -1018,21 +1017,21 @@ public void Should_call_onreset_when_manually_resetting_circuit() onBreakCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); onResetCalled.Should().Be(0); breaker.Reset(); onResetCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); } #region Tests of supplied parameters to onBreak delegate [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() + public async Task Should_call_onbreak_with_the_last_raised_exception() { Exception passedException = null; @@ -1043,19 +1042,19 @@ public void Should_call_onbreak_with_the_last_raised_exception() .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); passedException?.Should().BeOfType(); } [Fact] - public void Should_call_onbreak_with_a_state_of_closed() + public async Task Should_call_onbreak_with_a_state_of_closed() { CircuitState? transitionedState = null; @@ -1067,19 +1066,19 @@ public void Should_call_onbreak_with_a_state_of_closed() .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); transitionedState?.Should().Be(CircuitState.Closed); } [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() + public async Task Should_call_onbreak_with_a_state_of_half_open() { List transitionedStates = new List(); @@ -1096,17 +1095,17 @@ public void Should_call_onbreak_with_a_state_of_half_open() .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); @@ -1114,18 +1113,18 @@ public void Should_call_onbreak_with_a_state_of_half_open() // duration has passed, circuit now half open breaker.CircuitState.Should().Be(CircuitState.HalfOpen); // first call after duration raises an exception, so circuit should break again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); transitionedStates[0].Should().Be(CircuitState.Closed); transitionedStates[1].Should().Be(CircuitState.HalfOpen); } [Fact] - public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() + public async Task Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() { Exception passedException = null; @@ -1142,11 +1141,12 @@ public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwra Exception toRaiseAsInner = new DivideByZeroException(); Exception withInner = new AggregateException(toRaiseAsInner); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) + .Should().ThrowAsync(); + ex.Which.Should().BeSameAs(toRaiseAsInner); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1154,7 +1154,7 @@ public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwra } [Fact] - public void Should_call_onbreak_with_the_correct_timespan() + public async Task Should_call_onbreak_with_the_correct_timespan() { TimeSpan? passedBreakTimespan = null; @@ -1167,12 +1167,12 @@ public void Should_call_onbreak_with_the_correct_timespan() .Handle() .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); passedBreakTimespan.Should().Be(durationOfBreak); @@ -1205,7 +1205,7 @@ public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() #region Tests that supplied context is passed to stage-change delegates [Fact] - public void Should_call_onbreak_with_the_passed_context() + public async Task Should_call_onbreak_with_the_passed_context() { IDictionary contextData = null; @@ -1216,12 +1216,12 @@ public void Should_call_onbreak_with_the_passed_context() .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync( + await breaker.Awaiting(x => x.RaiseExceptionAsync( new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().Throw(); + )).Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1247,10 +1247,10 @@ public async Task Should_call_onreset_with_the_passed_context() .Handle() .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); @@ -1265,7 +1265,7 @@ public async Task Should_call_onreset_with_the_passed_context() } [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() { IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); @@ -1276,11 +1276,11 @@ public void Context_should_be_empty_if_execute_not_called_with_any_context_data( .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1288,7 +1288,7 @@ public void Context_should_be_empty_if_execute_not_called_with_any_context_data( } [Fact] - public void Should_create_new_context_for_each_call_to_execute() + public async Task Should_create_new_context_for_each_call_to_execute() { string contextValue = null; @@ -1304,12 +1304,12 @@ public void Should_create_new_context_for_each_call_to_execute() var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); contextValue.Should().Be("original_value"); @@ -1320,7 +1320,7 @@ public void Should_create_new_context_for_each_call_to_execute() // but not yet reset // first call after duration is successful, so circuit should reset - breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); breaker.CircuitState.Should().Be(CircuitState.Closed); contextValue.Should().Be("new_value"); } @@ -1342,14 +1342,14 @@ public void Should_initialise_LastException_to_null_on_creation() } [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + public async Task Should_set_LastException_on_handling_exception_even_when_not_breaking() { var breaker = Policy .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -1357,7 +1357,7 @@ public void Should_set_LastException_on_handling_exception_even_when_not_breakin } [Fact] - public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() + public async Task Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() { var breaker = Policy .HandleInner() @@ -1366,8 +1366,9 @@ public void Should_set_LastException_on_handling_inner_exception_even_when_not_b Exception toRaiseAsInner = new DivideByZeroException(); Exception withInner = new AggregateException(toRaiseAsInner); - breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) + .Should().ThrowAsync(); + ex.Which.Should().BeSameAs(toRaiseAsInner); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -1375,17 +1376,17 @@ public void Should_set_LastException_on_handling_inner_exception_even_when_not_b } [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() + public async Task Should_set_LastException_to_last_raised_exception_when_breaking() { var breaker = Policy .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1393,17 +1394,17 @@ public void Should_set_LastException_to_last_raised_exception_when_breaking() } [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() + public async Task Should_set_LastException_to_null_on_circuit_reset() { var breaker = Policy .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -1419,7 +1420,7 @@ public void Should_set_LastException_to_null_on_circuit_reset() #region Cancellation support [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1437,14 +1438,14 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca AttemptDuringWhichToCancel = null, }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1465,15 +1466,15 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1493,15 +1494,15 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1521,15 +1522,15 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use ActionObservesCancellation = true }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1548,26 +1549,26 @@ public void Should_report_faulting_from_faulting_action_execution_when_user_dele ActionObservesCancellation = false }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() { var breaker = Policy .Handle() .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); // Circuit is now broken. CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); @@ -1585,15 +1586,15 @@ public void Should_report_cancellation_when_both_open_circuit_and_cancellation() ActionObservesCancellation = false }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex2 = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex2.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() { // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. @@ -1612,20 +1613,20 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act int attemptsInvoked = 0; - breaker.Awaiting(x => x.ExecuteAsync(async _ => + var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => { attemptsInvoked++; await TaskHelper.EmptyTask; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1647,8 +1648,8 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().NotThrow(); + await breaker.Awaiting(action) + .Should().NotThrowAsync(); result.Should().BeTrue(); @@ -1656,7 +1657,7 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance } [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + public async Task Should_honour_and_report_cancellation_during_func_execution() { var breaker = Policy .Handle() @@ -1678,8 +1679,9 @@ public void Should_honour_and_report_cancellation_during_func_execution() }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + var ex = await breaker.Awaiting(action) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); result.Should().Be(null); diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs index a8aa3d864d9..4e897abbf5d 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs @@ -113,8 +113,8 @@ public async Task Should_open_circuit_with_the_last_handled_result_after_specifi .Should().Be(ResultPrimitive.Fault); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw>() + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync>() .WithMessage("The circuit is now open and is not allowing calls.") .Where(e => e.Result == ResultPrimitive.Fault); @@ -138,8 +138,8 @@ public async Task Should_open_circuit_with_the_last_handled_result_after_specifi breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw>() + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync>() .WithMessage("The circuit is now open and is not allowing calls.") .Where(e => e.Result == ResultPrimitive.FaultAgain); breaker.CircuitState.Should().Be(CircuitState.Open); @@ -160,8 +160,8 @@ public async Task Should_open_circuit_with_the_last_handled_result_after_specifi .ResultCode.Should().Be(ResultPrimitive.Fault); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(b => b.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Good))) - .Should().Throw>() + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Good))) + .Should().ThrowAsync>() .WithMessage("The circuit is now open and is not allowing calls.") .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); @@ -275,8 +275,8 @@ public async Task Should_halfopen_circuit_after_the_specified_duration_has_passe breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); @@ -308,8 +308,8 @@ public async Task Should_open_circuit_again_after_the_specified_duration_has_pas breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); @@ -321,8 +321,8 @@ public async Task Should_open_circuit_again_after_the_specified_duration_has_pas (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) .Should().Be(ResultPrimitive.Fault); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); } [Fact] @@ -346,8 +346,8 @@ public async Task Should_reset_circuit_after_the_specified_duration_has_passed_i breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); SystemClock.UtcNow = () => time.Add(durationOfBreak); @@ -368,8 +368,8 @@ public async Task Should_reset_circuit_after_the_specified_duration_has_passed_i .Should().Be(ResultPrimitive.Fault); breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); } @@ -477,9 +477,9 @@ public async Task Should_only_allow_single_execution_on_first_entering_halfopen_ bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(async () => { - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. @@ -493,7 +493,7 @@ public async Task Should_only_allow_single_execution_on_first_entering_halfopen_ firstExecutionActive = false; return ResultPrimitive.Good; - })).Should().NotThrow(); + })).Should().NotThrowAsync(); }, TaskCreationOptions.LongRunning); // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. @@ -581,9 +581,9 @@ public async Task Should_allow_single_execution_per_break_duration_in_halfopen_s bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(async () => { - breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.Awaiting(x => x.ExecuteAsync(async () => { firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. @@ -597,7 +597,7 @@ public async Task Should_allow_single_execution_per_break_duration_in_halfopen_s firstExecutionActive = false; return ResultPrimitive.Good; - })).Should().NotThrow(); + })).Should().NotThrowAsync(); }, TaskCreationOptions.LongRunning); // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. @@ -656,7 +656,7 @@ await breaker.ExecuteAsync(async () => #region Isolate and reset tests [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() + public async Task Should_open_circuit_and_block_calls_if_manual_override_open() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -674,8 +674,8 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit bool delegateExecutedWhenBroken = false; - breaker.Awaiting(b => b.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) - .Should().Throw(); + await breaker.Awaiting(b => b.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Isolated); breaker.LastException.Should().BeOfType(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -683,7 +683,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() } [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -702,13 +702,13 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope breaker.CircuitState.Should().Be(CircuitState.Isolated); bool delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) + .Should().ThrowAsync(); delegateExecutedWhenBroken.Should().BeFalse(); } [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() + public async Task Should_close_circuit_again_on_reset_after_manual_override() { var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -722,12 +722,12 @@ public void Should_close_circuit_again_on_reset_after_manual_override() breaker.Isolate(); breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); breaker.Reset(); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); } [Fact] @@ -751,15 +751,15 @@ public async Task Should_be_able_to_reset_automatically_opened_circuit_without_s breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // reset circuit, with no time having passed breaker.Reset(); SystemClock.UtcNow().Should().Be(time); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); } #endregion @@ -845,8 +845,8 @@ public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_f onBreakCalled.Should().Be(1); // call through circuit when already broken - should not retrigger onBreak - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -940,8 +940,8 @@ public async Task Should_call_onreset_when_automatically_closing_circuit_but_not onBreakCalled.Should().Be(1); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -953,13 +953,13 @@ public async Task Should_call_onreset_when_automatically_closing_circuit_but_not onResetCalled.Should().Be(0); // first call after duration is successful, so circuit should reset - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().Be(1); } [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() + public async Task Should_not_call_onreset_on_successive_successful_calls() { Action, TimeSpan> onBreak = (_, _) => { }; bool onResetCalled = false; @@ -971,11 +971,11 @@ public void Should_not_call_onreset_on_successive_successful_calls() onResetCalled.Should().BeFalse(); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().BeFalse(); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().BeFalse(); } @@ -1010,8 +1010,8 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal onBreakCalled.Should().Be(1); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -1020,8 +1020,8 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state // first call after duration is successful, so circuit should reset - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().NotThrowAsync(); onHalfOpenCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Closed); onResetCalled.Should().Be(1); @@ -1057,8 +1057,8 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal onBreakCalled.Should().Be(1); // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); @@ -1069,7 +1069,7 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal } [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() + public async Task Should_call_onreset_when_manually_resetting_circuit() { int onBreakCalled = 0; int onResetCalled = 0; @@ -1090,16 +1090,16 @@ public void Should_call_onreset_when_manually_resetting_circuit() onBreakCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); onResetCalled.Should().Be(0); breaker.Reset(); onResetCalled.Should().Be(1); breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().NotThrow(); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().NotThrowAsync(); } #region Tests of supplied parameters to onBreak delegate @@ -1411,7 +1411,7 @@ public async Task Should_execute_action_when_non_faulting_and_cancellationToken_ } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1431,18 +1431,18 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1461,17 +1461,17 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy @@ -1490,11 +1490,11 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use ActionObservesCancellation = true }; - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } @@ -1535,8 +1535,8 @@ public async Task Should_report_cancellation_when_both_open_circuit_and_cancella (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) .Should().Be(ResultPrimitive.Fault); - breaker.Awaiting(x => x.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw() + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync() .WithMessage("The circuit is now open and is not allowing calls."); // Circuit is now broken. @@ -1554,17 +1554,17 @@ public async Task Should_report_cancellation_when_both_open_circuit_and_cancella ActionObservesCancellation = false }; - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex2 = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex2.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() { // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. @@ -1583,15 +1583,15 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act int attemptsInvoked = 0; - breaker.Awaiting(x => x.ExecuteAsync(async _ => + var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => { attemptsInvoked++; await TaskHelper.EmptyTask; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); return ResultPrimitive.Good; }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); attemptsInvoked.Should().Be(1); } diff --git a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs index 82da36f3d21..9f2c7493e11 100644 --- a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs @@ -26,15 +26,15 @@ public void Should_be_able_to_construct_active_policy() } [Fact] - public void Active_policy_should_execute() + public async Task Active_policy_should_execute() { bool preExecuted = false; AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); bool executed = false; - policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; return Task.CompletedTask; })) - .Should().NotThrow(); + await policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; return Task.CompletedTask; })) + .Should().NotThrowAsync(); executed.Should().BeTrue(); preExecuted.Should().BeTrue(); @@ -57,7 +57,7 @@ public void Should_be_able_to_construct_reactive_policy() } [Fact] - public void Reactive_policy_should_handle_exception() + public async Task Reactive_policy_should_handle_exception() { Exception handled = null; AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); @@ -65,19 +65,20 @@ public void Reactive_policy_should_handle_exception() Exception toThrow = new InvalidOperationException(); bool executed = false; - policy.Awaiting(x => x.ExecuteAsync(() => + var ex = await policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; throw toThrow; })) - .Should().Throw().Which.Should().Be(toThrow); + .Should().ThrowAsync(); + ex.Which.Should().Be(toThrow); executed.Should().BeTrue(); handled.Should().Be(toThrow); } [Fact] - public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() + public async Task Reactive_policy_should_be_able_to_ignore_unhandled_exception() { Exception handled = null; AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); @@ -85,12 +86,13 @@ public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() Exception toThrow = new NotImplementedException(); bool executed = false; - policy.Awaiting(x => x.ExecuteAsync(() => + var ex = await policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; throw toThrow; })) - .Should().Throw().Which.Should().Be(toThrow); + .Should().ThrowAsync(); + ex.Which.Should().Be(toThrow); executed.Should().BeTrue(); handled.Should().Be(null); diff --git a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs index f48cfffd514..539de07c8d9 100644 --- a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs @@ -27,14 +27,14 @@ public void Should_be_able_to_construct_active_policy() } [Fact] - public void Active_policy_should_execute() + public async Task Active_policy_should_execute() { bool preExecuted = false; AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); bool executed = false; - policy.Awaiting(x => x.ExecuteAsync(async () => { executed = true; await Task.CompletedTask; return ResultPrimitive.Undefined; })) - .Should().NotThrow(); + await policy.Awaiting(x => x.ExecuteAsync(async () => { executed = true; await Task.CompletedTask; return ResultPrimitive.Undefined; })) + .Should().NotThrowAsync(); executed.Should().BeTrue(); preExecuted.Should().BeTrue(); diff --git a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs index a71db753b4a..5989fb1099d 100644 --- a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs @@ -104,7 +104,7 @@ public async Task Should_not_execute_fallback_when_executed_delegate_does_not_th } [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() + public async Task Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -113,13 +113,13 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_exception_ .Handle() .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() + public async Task Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -128,14 +128,14 @@ public void Should_execute_fallback_when_executed_delegate_throws_exception_hand .Handle() .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); fallbackActionExecuted.Should().BeTrue(); } [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() + public async Task Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -145,14 +145,14 @@ public void Should_execute_fallback_when_executed_delegate_throws_one_of_excepti .Or() .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); fallbackActionExecuted.Should().BeTrue(); } [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() + public async Task Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -162,13 +162,13 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_exception_ .Or() .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() + public async Task Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -177,13 +177,13 @@ public void Should_not_execute_fallback_when_exception_thrown_does_not_match_han .Handle(_ => false) .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() + public async Task Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -193,13 +193,13 @@ public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any .Or(_ => false) .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() + public async Task Should_execute_fallback_when_exception_thrown_matches_handling_predicates() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -208,14 +208,14 @@ public void Should_execute_fallback_when_exception_thrown_matches_handling_predi .Handle(_ => true) .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); fallbackActionExecuted.Should().BeTrue(); } [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() + public async Task Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -225,13 +225,13 @@ public void Should_execute_fallback_when_exception_thrown_matches_one_of_handlin .Or() .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); fallbackActionExecuted.Should().BeTrue(); } [Fact] - public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() + public async Task Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => @@ -244,20 +244,21 @@ public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_e .Handle() .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync((e, _) => e.HelpLink = "FromExecuteDelegate")) - .Should().Throw().And.HelpLink.Should().Be("FromFallbackAction"); + var ex = await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync((e, _) => e.HelpLink = "FromExecuteDelegate")) + .Should().ThrowAsync(); + ex.And.HelpLink.Should().Be("FromFallbackAction"); fallbackActionExecuted.Should().BeTrue(); } [Fact] - public void Should_throw_for_generic_method_execution_on_non_generic_policy() + public async Task Should_throw_for_generic_method_execution_on_non_generic_policy() { var fallbackPolicy = Policy .Handle() .FallbackAsync(_ => TaskHelper.EmptyTask); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))).Should().Throw(); + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))).Should().ThrowAsync(); } #endregion @@ -307,7 +308,7 @@ public async Task Should_not_call_onFallback_when_executed_delegate_does_not_thr #region Context passing tests [Fact] - public void Should_call_onFallback_with_the_passed_context() + public async Task Should_call_onFallback_with_the_passed_context() { Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; @@ -319,9 +320,9 @@ public void Should_call_onFallback_with_the_passed_context() .Handle() .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextData.Should() .ContainKeys("key1", "key2").And @@ -329,7 +330,7 @@ public void Should_call_onFallback_with_the_passed_context() } [Fact] - public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + public async Task Should_call_onFallback_with_the_passed_context_when_execute_and_capture() { Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; @@ -341,9 +342,9 @@ public void Should_call_onFallback_with_the_passed_context_when_execute_and_capt .Handle() .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextData.Should() .ContainKeys("key1", "key2").And @@ -351,7 +352,7 @@ public void Should_call_onFallback_with_the_passed_context_when_execute_and_capt } [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() + public async Task Should_call_onFallback_with_independent_context_for_independent_calls() { Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; @@ -364,11 +365,11 @@ public void Should_call_onFallback_with_independent_context_for_independent_call .Or() .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key = "value1" }.AsDictionary())) - .Should().NotThrow(); + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key = "value1" }.AsDictionary())) + .Should().NotThrowAsync(); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new DivideByZeroException(), new { key = "value2" }.AsDictionary())) - .Should().NotThrow(); + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new DivideByZeroException(), new { key = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); contextData.Count.Should().Be(2); contextData.Keys.Should().Contain(typeof(ArgumentNullException)); @@ -399,7 +400,7 @@ public async Task Context_should_be_empty_if_execute_not_called_with_any_context } [Fact] - public void Should_call_fallbackAction_with_the_passed_context() + public async Task Should_call_fallbackAction_with_the_passed_context() { IDictionary contextData = null; @@ -411,9 +412,9 @@ public void Should_call_fallbackAction_with_the_passed_context() .Handle() .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextData.Should() .ContainKeys("key1", "key2").And @@ -421,7 +422,7 @@ public void Should_call_fallbackAction_with_the_passed_context() } [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + public async Task Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() { IDictionary contextData = null; @@ -433,9 +434,9 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ .Handle() .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextData.Should() .ContainKeys("key1", "key2").And @@ -468,7 +469,7 @@ public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_calle #region Exception passing tests [Fact] - public void Should_call_fallbackAction_with_the_exception() + public async Task Should_call_fallbackAction_with_the_exception() { Exception fallbackException = null; @@ -481,14 +482,14 @@ public void Should_call_fallbackAction_with_the_exception() .FallbackAsync(fallbackFunc, onFallback); Exception instanceToThrow = new ArgumentNullException("myParam"); - fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrow(); + await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrowAsync(); fallbackException.Should().Be(instanceToThrow); } [Fact] - public void Should_call_fallbackAction_with_the_exception_when_execute_and_capture() + public async Task Should_call_fallbackAction_with_the_exception_when_execute_and_capture() { Exception fallbackException = null; @@ -500,15 +501,15 @@ public void Should_call_fallbackAction_with_the_exception_when_execute_and_captu .Handle() .FallbackAsync(fallbackFunc, onFallback); - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(() => throw new ArgumentNullException())) - .Should().NotThrow(); + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(() => throw new ArgumentNullException())) + .Should().NotThrowAsync(); fallbackException.Should().NotBeNull() .And.BeOfType(typeof(ArgumentNullException)); } [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() + public async Task Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() { Exception fallbackException = null; @@ -522,14 +523,14 @@ public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrappe Exception instanceToCapture = new ArgumentNullException("myParam"); Exception instanceToThrow = new Exception(String.Empty, instanceToCapture); - fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrow(); + await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrowAsync(); fallbackException.Should().Be(instanceToCapture); } [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() + public async Task Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() { Exception fallbackException = null; @@ -543,14 +544,14 @@ public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_excep Exception instanceToCapture = new ArgumentNullException("myParam"); Exception instanceToThrow = new AggregateException(instanceToCapture); - fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrow(); + await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrowAsync(); fallbackException.Should().Be(instanceToCapture); } [Fact] - public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() + public async Task Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() { Exception fallbackException = null; @@ -562,8 +563,8 @@ public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhan .Handle() .FallbackAsync(fallbackFunc, onFallback); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => throw new ArgumentNullException())) - .Should().Throw(); + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => throw new ArgumentNullException())) + .Should().ThrowAsync(); fallbackException.Should().BeNull(); } @@ -573,7 +574,7 @@ public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhan #region Cancellation tests [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -594,15 +595,15 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -623,15 +624,15 @@ public void Should_execute_fallback_when_faulting_and_cancellationToken_not_canc AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeTrue(); } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -654,9 +655,9 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); fallbackActionExecuted.Should().BeFalse(); @@ -664,7 +665,7 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex } [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + public async Task Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -686,16 +687,16 @@ public void Should_report_cancellation_and_not_execute_fallback_during_otherwise ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; @@ -718,15 +719,15 @@ public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeTrue(); } [Fact] - public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { bool fallbackActionExecuted = false; @@ -749,15 +750,15 @@ public void Should_not_report_cancellation_and_not_execute_fallback_if_non_fault ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { bool fallbackActionExecuted = false; @@ -781,15 +782,15 @@ public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_exe ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeFalse(); } [Fact] - public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { bool fallbackActionExecuted = false; @@ -813,8 +814,8 @@ public void Should_handle_handled_fault_and_execute_fallback_following_faulting_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeTrue(); diff --git a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs index 5c88b6fa565..f5bd03da6d0 100644 --- a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs @@ -460,7 +460,7 @@ public void Should_call_fallbackAction_with_the_passed_context() } [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + public async Task Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() { IDictionary contextData = null; @@ -472,9 +472,9 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ .HandleResult(ResultPrimitive.Fault) .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextData.Should() .ContainKeys("key1", "key2").And @@ -636,7 +636,7 @@ public async Task Should_execute_fallback_when_faulting_and_cancellationToken_no } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; @@ -659,9 +659,9 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); fallbackActionExecuted.Should().BeFalse(); @@ -669,7 +669,7 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex } [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + public async Task Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; @@ -691,9 +691,9 @@ public void Should_report_cancellation_and_not_execute_fallback_during_otherwise ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); fallbackActionExecuted.Should().BeFalse(); diff --git a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs index 78b392604ea..70116521bd1 100644 --- a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs +++ b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs @@ -30,7 +30,7 @@ public async Task Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_ (await genericPolicy.ExecuteAsync(deleg)).Should().Be(ResultPrimitive.Good); breaker.Isolate(); - genericPolicy.Awaiting(p => p.ExecuteAsync(deleg)).Should().Throw(); + await genericPolicy.Awaiting(p => p.ExecuteAsync(deleg)).Should().ThrowAsync(); } } diff --git a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs index ccb5f15d200..c11807ebaba 100644 --- a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs @@ -1,4 +1,5 @@ using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Polly.Utilities; using Xunit; @@ -8,19 +9,19 @@ namespace Polly.Specs.NoOp public class NoOpAsyncSpecs { [Fact] - public void Should_execute_user_delegate() + public async Task Should_execute_user_delegate() { var policy = Policy.NoOpAsync(); bool executed = false; - policy.Awaiting(p => p.ExecuteAsync(() => { executed = true; return TaskHelper.EmptyTask; })) - .Should().NotThrow(); + await policy.Awaiting(p => p.ExecuteAsync(() => { executed = true; return TaskHelper.EmptyTask; })) + .Should().NotThrowAsync(); executed.Should().BeTrue(); } [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + public async Task Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() { var policy = Policy.NoOpAsync(); @@ -30,9 +31,9 @@ public void Should_execute_user_delegate_without_adding_extra_cancellation_behav { cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync( + await policy.Awaiting(p => p.ExecuteAsync( _ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) - .Should().NotThrow(); + .Should().NotThrowAsync(); } executed.Should().BeTrue(); diff --git a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs index 50979d539d0..10f81a209dd 100644 --- a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs @@ -10,21 +10,21 @@ namespace Polly.Specs.NoOp public class NoOpTResultAsyncSpecs { [Fact] - public void Should_execute_user_delegate() + public async Task Should_execute_user_delegate() { var policy = Policy.NoOpAsync(); int? result = null; Func, Task> action = async p => result = await p.ExecuteAsync(() => Task.FromResult((int?)10)); - policy.Awaiting(action) - .Should().NotThrow(); + await policy.Awaiting(action) + .Should().NotThrowAsync(); result.HasValue.Should().BeTrue(); result.Should().Be(10); } [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + public async Task Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() { var policy = Policy.NoOpAsync(); int? result = null; @@ -34,8 +34,8 @@ public void Should_execute_user_delegate_without_adding_extra_cancellation_behav cts.Cancel(); Func, Task> action = async p => result = await p.ExecuteAsync(_ => Task.FromResult((int?)10), cts.Token); - policy.Awaiting(action) - .Should().NotThrow(); + await policy.Awaiting(action) + .Should().NotThrowAsync(); } result.HasValue.Should().BeTrue(); diff --git a/src/Polly.Specs/PolicyAsyncSpecs.cs b/src/Polly.Specs/PolicyAsyncSpecs.cs index 4835ac5154e..ac645347500 100644 --- a/src/Polly.Specs/PolicyAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyAsyncSpecs.cs @@ -165,49 +165,49 @@ public async Task Executing_the_policy_function_and_failing_with_an_unhandled_ex #region Context tests [Fact] - public void Executing_the_policy_action_should_throw_when_context_data_is_null() + public async Task Executing_the_policy_action_should_throw_when_context_data_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) - .Should().Throw(); + await policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) + .Should().ThrowAsync(); } [Fact] - public void Executing_the_policy_action_should_throw_when_context_is_null() + public async Task Executing_the_policy_action_should_throw_when_context_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); + var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() + public async Task Executing_the_policy_function_should_throw_when_context_data_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (IDictionary)null)) - .Should().Throw(); + await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (IDictionary)null)) + .Should().ThrowAsync(); } [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() + public async Task Executing_the_policy_function_should_throw_when_context_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); + var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } [Fact] @@ -225,49 +225,49 @@ public async Task Executing_the_policy_function_should_pass_context_to_executed_ } [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() + public async Task Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) - .Should().Throw(); + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) + .Should().ThrowAsync(); } [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() + public async Task Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (IDictionary)null)) - .Should().Throw(); + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (IDictionary)null)) + .Should().ThrowAsync(); } [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() { var policy = Policy .Handle() .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } [Fact] diff --git a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs index 94ac159d8c7..d9638ef5ca3 100644 --- a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs @@ -96,38 +96,38 @@ public async Task Executing_the_policy_function_and_returning_an_unhandled_resul [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() + public async Task Executing_the_policy_function_should_throw_when_context_data_is_null() { var policy = Policy .HandleResult(ResultPrimitive.Fault) .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (IDictionary)null)) - .Should().Throw(); + await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (IDictionary)null)) + .Should().ThrowAsync(); } [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() + public async Task Executing_the_policy_function_should_throw_when_context_is_null() { var policy = Policy .HandleResult(ResultPrimitive.Fault) .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); + var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() { var policy = Policy .HandleResult(ResultPrimitive.Fault) .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } [Fact] @@ -145,15 +145,15 @@ public async Task Executing_the_policy_function_should_pass_context_to_executed_ } [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() { var policy = Policy .HandleResult(ResultPrimitive.Fault) .RetryAsync((_, _, _) => { }); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } [Fact] diff --git a/src/Polly.Specs/Polly.Specs.csproj b/src/Polly.Specs/Polly.Specs.csproj index 6ad7be03aec..b1d6577c94b 100644 --- a/src/Polly.Specs/Polly.Specs.csproj +++ b/src/Polly.Specs/Polly.Specs.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs index e05b5948c05..57dab67b038 100644 --- a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs @@ -282,7 +282,10 @@ public void Policies_Should_Be_Added_To_The_Registry_When_Using_Collection_Initi {key, policy} }; - testRegistry.Should().Equal(new KeyValuePair(key, policy)); + testRegistry.Should().Equal(new Dictionary + { + {key, policy} + }); } #endregion } diff --git a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs index da10076de10..00704b7ad50 100644 --- a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs @@ -29,141 +29,141 @@ public void Should_throw_when_retry_count_is_less_than_zero_without_context() } [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() + public async Task Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() { var policy = Policy .Handle() .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() { var policy = Policy .Handle() .Or() .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() + public async Task Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() { var policy = Policy .Handle() .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() { var policy = Policy .Handle() .Or() .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_then_retry_count() + public async Task Should_throw_when_specified_exception_thrown_more_times_then_retry_count() { var policy = Policy .Handle() .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() + public async Task Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() { var policy = Policy .Handle() .Or() .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() { var policy = Policy .Handle() .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() { var policy = Policy .Handle() .Or() .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() { var policy = Policy .Handle(_ => false) .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() { var policy = Policy .Handle(_ => false) .Or(_ => false) .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() { var policy = Policy .Handle(_ => true) .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() { var policy = Policy .Handle(_ => true) .Or(_ => true) .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] @@ -185,7 +185,7 @@ public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count [Fact] public async Task Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; var retryExceptions = new List(); var policy = Policy @@ -218,7 +218,7 @@ public async Task Should_call_onretry_with_a_handled_innerexception() } [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() + public async Task Should_not_call_onretry_when_no_retries_are_performed() { var retryCounts = new List(); @@ -226,25 +226,25 @@ public void Should_not_call_onretry_when_no_retries_are_performed() .Handle() .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); retryCounts.Should() .BeEmpty(); } [Fact] - public void Should_create_new_state_for_each_call_to_policy() + public async Task Should_create_new_state_for_each_call_to_policy() { var policy = Policy .Handle() .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] @@ -266,7 +266,7 @@ public void Should_call_onretry_with_the_passed_context() } [Fact] - public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() + public async Task Should_call_onretry_with_the_passed_context_when_execute_and_capture() { IDictionary contextData = null; @@ -274,9 +274,9 @@ public void Should_call_onretry_with_the_passed_context_when_execute_and_capture .Handle() .RetryAsync((_, _, context) => contextData = context); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => { throw new DivideByZeroException(); }, + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => { throw new DivideByZeroException(); }, new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextData.Should() .ContainKeys("key1", "key2").And @@ -284,7 +284,7 @@ public void Should_call_onretry_with_the_passed_context_when_execute_and_capture } [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_data() + public async Task Context_should_be_empty_if_execute_not_called_with_any_data() { Context capturedContext = null; @@ -292,7 +292,7 @@ public void Context_should_be_empty_if_execute_not_called_with_any_data() .Handle() .RetryAsync((_, _, context) => capturedContext = context); - policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); capturedContext.Should() .BeEmpty(); @@ -321,7 +321,7 @@ public void Should_create_new_context_for_each_call_to_execute() } [Fact] - public void Should_create_new_context_for_each_call_to_execute_and_capture() + public async Task Should_create_new_context_for_each_call_to_execute_and_capture() { string contextValue = null; @@ -329,21 +329,21 @@ public void Should_create_new_context_for_each_call_to_execute_and_capture() .Handle() .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), new { key = "original_value" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextValue.Should().Be("original_value"); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), new { key = "new_value" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrowAsync(); contextValue.Should().Be("new_value"); } [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero() + public async Task Should_not_call_onretry_when_retry_count_is_zero() { bool retryInvoked = false; @@ -353,8 +353,8 @@ public void Should_not_call_onretry_when_retry_count_is_zero() .Handle() .RetryAsync(0, onRetry); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); retryInvoked.Should().BeFalse(); } @@ -362,7 +362,7 @@ public void Should_not_call_onretry_when_retry_count_is_zero() #region Async and cancellation tests [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() + public async Task Should_wait_asynchronously_for_async_onretry_delegate() { // This test relates to https://github.com/App-vNext/Polly/issues/107. // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning to Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx @@ -382,12 +382,12 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; }); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { executeDelegateInvocations++; await TaskHelper.EmptyTask; throw new DivideByZeroException(); - })).Should().Throw(); + })).Should().ThrowAsync(); while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. @@ -396,7 +396,7 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() } [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -414,14 +414,14 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -439,14 +439,14 @@ public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_can AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); attemptsInvoked.Should().Be(1 + 3); } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var policy = Policy .Handle() @@ -466,15 +466,15 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -493,15 +493,15 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -520,15 +520,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .Handle() @@ -547,15 +547,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -574,15 +574,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .Handle() @@ -601,15 +601,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { var policy = Policy .Handle() @@ -628,15 +628,15 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1 + 3); } [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { var policy = Policy .Handle() @@ -655,14 +655,14 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); attemptsInvoked.Should().Be(1 + 3); } [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; @@ -684,15 +684,15 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -713,8 +713,8 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().NotThrow(); + await policy.Awaiting(action) + .Should().NotThrowAsync(); result.Should().BeTrue(); @@ -722,7 +722,7 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance } [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + public async Task Should_honour_and_report_cancellation_during_func_execution() { var policy = Policy .Handle() @@ -744,8 +744,8 @@ public void Should_honour_and_report_cancellation_during_func_execution() }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); result.Should().Be(null); diff --git a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs index a96eb9e3c95..81487a8c303 100644 --- a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs @@ -16,101 +16,101 @@ namespace Polly.Specs.Retry public class RetryForeverAsyncSpecs { [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + public async Task Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() { var policy = Policy .Handle() .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + public async Task Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() { var policy = Policy .Handle() .Or() .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() { var policy = Policy .Handle() .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() { var policy = Policy .Handle() .Or() .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() { var policy = Policy .Handle(_ => false) .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() { var policy = Policy .Handle(_ => false) .Or(_ => false) .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() { var policy = Policy .Handle(_ => true) .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() { var policy = Policy .Handle(_ => true) .Or(_ => true) .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] public async Task Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] {"Exception #1", "Exception #2", "Exception #3"}; + var expectedExceptions = new string[] {"Exception #1", "Exception #2", "Exception #3"}; var retryExceptions = new List(); var policy = Policy @@ -160,7 +160,7 @@ public void Should_call_onretry_on_each_retry_with_the_current_retry_count() } [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_data() + public async Task Context_should_be_empty_if_execute_not_called_with_any_data() { Context capturedContext = null; @@ -168,7 +168,7 @@ public void Context_should_be_empty_if_execute_not_called_with_any_data() .Handle() .RetryForeverAsync((_, context) => capturedContext = context); - policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); capturedContext.Should() .BeEmpty(); @@ -197,7 +197,7 @@ public void Should_create_new_context_for_each_call_to_execute() } [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() + public async Task Should_not_call_onretry_when_no_retries_are_performed() { var retryExceptions = new List(); @@ -205,15 +205,15 @@ public void Should_not_call_onretry_when_no_retries_are_performed() .Handle() .RetryForeverAsync(exception => retryExceptions.Add(exception)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); retryExceptions.Should() .BeEmpty(); } [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() + public async Task Should_wait_asynchronously_for_async_onretry_delegate() { // This test relates to https://github.com/App-vNext/Polly/issues/107. // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx @@ -233,12 +233,12 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; }); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { executeDelegateInvocations++; await TaskHelper.EmptyTask; if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } - })).Should().NotThrow(); + })).Should().NotThrowAsync(); while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. @@ -247,7 +247,7 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() } [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -265,14 +265,14 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var policy = Policy .Handle() @@ -292,15 +292,15 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -319,15 +319,15 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -346,15 +346,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .Handle() @@ -373,15 +373,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -400,15 +400,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .Handle() @@ -427,15 +427,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; @@ -458,15 +458,15 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -487,8 +487,8 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().NotThrow(); + await policy.Awaiting(action) + .Should().NotThrowAsync(); result.Should().BeTrue(); @@ -496,7 +496,7 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance } [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + public async Task Should_honour_and_report_cancellation_during_func_execution() { var policy = Policy .Handle() @@ -518,8 +518,8 @@ public void Should_honour_and_report_cancellation_during_func_execution() }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); result.Should().Be(null); diff --git a/src/Polly.Specs/Retry/RetryForeverSpecs.cs b/src/Polly.Specs/Retry/RetryForeverSpecs.cs index 811eaf9e97f..bb6cdfe5c14 100644 --- a/src/Polly.Specs/Retry/RetryForeverSpecs.cs +++ b/src/Polly.Specs/Retry/RetryForeverSpecs.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using FluentAssertions; using Polly.Specs.Helpers; using Xunit; @@ -70,14 +71,14 @@ public void Should_throw_when_exception_thrown_is_not_the_specified_exception_ty } [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() { var policy = Policy .Handle() .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] @@ -127,14 +128,14 @@ public void Should_not_throw_when_specified_exception_predicate_is_satisfied() } [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied_async() + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied_async() { var policy = Policy .Handle(_ => true) .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] @@ -150,21 +151,21 @@ public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_ } [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() { var policy = Policy .Handle(_ => true) .Or(_ => true) .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] public void Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] {"Exception #1", "Exception #2", "Exception #3"}; + var expectedExceptions = new string[] {"Exception #1", "Exception #2", "Exception #3"}; var retryExceptions = new List(); var policy = Policy diff --git a/src/Polly.Specs/Retry/RetrySpecs.cs b/src/Polly.Specs/Retry/RetrySpecs.cs index 705fd6b18a1..2d5f6aee1bc 100644 --- a/src/Polly.Specs/Retry/RetrySpecs.cs +++ b/src/Polly.Specs/Retry/RetrySpecs.cs @@ -219,7 +219,7 @@ public void Should_call_onretry_on_each_retry_with_the_current_retry_count() [Fact] public void Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; var retryExceptions = new List(); var policy = Policy diff --git a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs index 2f4b940a48f..8d03d0b7110 100644 --- a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs +++ b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs @@ -482,7 +482,7 @@ public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_n } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -501,19 +501,19 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -531,19 +531,19 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good, ResultPrimitive.Good, ResultPrimitive.Good, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -561,19 +561,19 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -591,19 +591,19 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -621,19 +621,19 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -651,19 +651,19 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -681,14 +681,14 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1 + 3); } @@ -724,7 +724,7 @@ public async Task Should_report_faulting_from_faulting_last_retry_execution_when } [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; @@ -745,13 +745,13 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } diff --git a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs index 97d17ae1103..85ec50b620a 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs @@ -51,7 +51,7 @@ public void Should_throw_when_onretry_action_is_null_without_context() } [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + public async Task Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() { var policy = Policy .Handle() @@ -62,12 +62,12 @@ public void Should_not_throw_when_specified_exception_thrown_same_number_of_time 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() { var policy = Policy .Handle() @@ -79,12 +79,12 @@ public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_nu 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + public async Task Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() { var policy = Policy .Handle() @@ -95,12 +95,12 @@ public void Should_not_throw_when_specified_exception_thrown_less_number_of_time 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_there_are_sleep_durations() + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_there_are_sleep_durations() { var policy = Policy .Handle() @@ -112,12 +112,12 @@ public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_nu 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().NotThrowAsync(); } [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() + public async Task Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() { var policy = Policy .Handle() @@ -128,12 +128,12 @@ public void Should_throw_when_specified_exception_thrown_more_times_than_there_a 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_than_there_are_sleep_durations() + public async Task Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_than_there_are_sleep_durations() { var policy = Policy .Handle() @@ -145,58 +145,58 @@ public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_ti 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() { var policy = Policy .Handle() .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() { var policy = Policy .Handle() .Or() .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() { var policy = Policy .Handle(_ => false) .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() { var policy = Policy .Handle(_ => false) .Or(_ => false) .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() { var policy = Policy .Handle(_ => true) @@ -205,12 +205,12 @@ public void Should_not_throw_when_specified_exception_predicate_is_satisfied() 1.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() { var policy = Policy .Handle(_ => true) @@ -220,8 +220,8 @@ public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_ 1.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] @@ -251,7 +251,7 @@ public async Task Should_sleep_for_the_specified_duration_each_retry_when_specif } [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() + public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() { var totalTimeSlept = 0; @@ -270,8 +270,8 @@ public void Should_sleep_for_the_specified_duration_each_retry_when_specified_ex return TaskHelper.EmptyTask; }; - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); totalTimeSlept.Should() .Be(1 + 2 + 3); @@ -304,7 +304,7 @@ public async Task Should_sleep_for_the_specified_duration_each_retry_when_specif } [Fact] - public void Should_not_sleep_if_no_retries() + public async Task Should_not_sleep_if_no_retries() { var totalTimeSlept = 0; @@ -318,8 +318,8 @@ public void Should_not_sleep_if_no_retries() return TaskHelper.EmptyTask; }; - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); totalTimeSlept.Should() .Be(0); @@ -355,7 +355,7 @@ public async Task Should_call_onretry_on_each_retry_with_the_current_timespan() [Fact] public async Task Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; var retryExceptions = new List(); var policy = Policy @@ -397,7 +397,7 @@ public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count } [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() + public async Task Should_not_call_onretry_when_no_retries_are_performed() { var retryExceptions = new List(); @@ -405,14 +405,14 @@ public void Should_not_call_onretry_when_no_retries_are_performed() .Handle() .WaitAndRetryAsync(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); retryExceptions.Should().BeEmpty(); } [Fact] - public void Should_create_new_state_for_each_call_to_policy() + public async Task Should_create_new_state_for_each_call_to_policy() { var policy = Policy .Handle() @@ -421,11 +421,11 @@ public void Should_create_new_state_for_each_call_to_policy() 1.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] @@ -452,7 +452,7 @@ await policy.RaiseExceptionAsync( } [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_data() + public async Task Context_should_be_empty_if_execute_not_called_with_any_data() { Context capturedContext = null; @@ -464,7 +464,7 @@ public void Context_should_be_empty_if_execute_not_called_with_any_data() 2.Seconds(), 3.Seconds() }, (_, _, context) => capturedContext = context); - policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); capturedContext.Should() .BeEmpty(); @@ -651,7 +651,7 @@ await policy.ExecuteAsync(async (context, _) => } [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero() + public async Task Should_not_call_onretry_when_retry_count_is_zero() { bool retryInvoked = false; @@ -661,14 +661,14 @@ public void Should_not_call_onretry_when_retry_count_is_zero() .Handle() .WaitAndRetryAsync(0, _ => TimeSpan.FromSeconds(1), onRetry); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); retryInvoked.Should().BeFalse(); } [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() + public async Task Should_wait_asynchronously_for_async_onretry_delegate() { // This test relates to https://github.com/App-vNext/Polly/issues/107. // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx @@ -690,12 +690,12 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; }); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { executeDelegateInvocations++; await TaskHelper.EmptyTask; throw new DivideByZeroException(); - })).Should().Throw(); + })).Should().ThrowAsync(); while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. @@ -704,7 +704,7 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() } [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -722,14 +722,14 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -747,14 +747,14 @@ public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_can AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); attemptsInvoked.Should().Be(1 + 3); } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var policy = Policy .Handle() @@ -774,15 +774,15 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -801,15 +801,15 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -828,15 +828,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .Handle() @@ -855,15 +855,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { var policy = Policy .Handle() @@ -882,15 +882,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { var policy = Policy .Handle() @@ -909,15 +909,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { var policy = Policy .Handle() @@ -936,15 +936,15 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1 + 3); } [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { var policy = Policy .Handle() @@ -963,14 +963,14 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); attemptsInvoked.Should().Be(1 + 3); } [Fact] - public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() + public async Task Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; @@ -999,19 +999,19 @@ public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandr cancellationTokenSource.CancelAfter(shimTimeSpan); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); watch.Stop(); attemptsInvoked.Should().Be(1); watch.Elapsed.Should().BeLessThan(retryDelay); - watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: (int)(shimTimeSpan.TotalMilliseconds) / 2); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. + watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: TimeSpan.FromMilliseconds((int)shimTimeSpan.TotalMilliseconds / 2)); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. } [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; @@ -1034,15 +1034,15 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { var policy = Policy .Handle() @@ -1063,8 +1063,8 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().NotThrow(); + await policy.Awaiting(action) + .Should().NotThrowAsync(); result.Should().BeTrue(); @@ -1072,7 +1072,7 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance } [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + public async Task Should_honour_and_report_cancellation_during_func_execution() { var policy = Policy .Handle() @@ -1094,8 +1094,8 @@ public void Should_honour_and_report_cancellation_during_func_execution() }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); result.Should().Be(null); diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs index 3a312efe95d..9abd93b3d1c 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs @@ -78,30 +78,30 @@ public void Should_throw_when_onretry_action_is_null_with_context() } [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + public async Task Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() { var policy = Policy .Handle() .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + public async Task Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() { var policy = Policy .Handle() .Or() .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() { Func provider = _ => TimeSpan.Zero; @@ -109,12 +109,12 @@ public void Should_throw_when_exception_thrown_is_not_the_specified_exception_ty .Handle() .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() { Func provider = _ => TimeSpan.Zero; @@ -123,12 +123,12 @@ public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_excep .Or() .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() { Func provider = _ => TimeSpan.Zero; @@ -136,12 +136,12 @@ public void Should_throw_when_specified_exception_predicate_is_not_satisfied() .Handle(_ => false) .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() { Func provider = _ => TimeSpan.Zero; @@ -150,12 +150,12 @@ public void Should_throw_when_none_of_the_specified_exception_predicates_are_sat .Or(_ => false) .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() { Func provider = _ => 1.Seconds(); @@ -163,12 +163,12 @@ public void Should_not_throw_when_specified_exception_predicate_is_satisfied() .Handle(_ => true) .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() { Func provider = _ => 1.Seconds(); @@ -177,12 +177,12 @@ public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_ .Or(_ => true) .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] - public void Should_not_sleep_if_no_retries() + public async Task Should_not_sleep_if_no_retries() { Func provider = _ => 1.Seconds(); @@ -194,8 +194,8 @@ public void Should_not_sleep_if_no_retries() SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); totalTimeSlept.Should() .Be(0); @@ -204,7 +204,7 @@ public void Should_not_sleep_if_no_retries() [Fact] public async Task Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; var retryExceptions = new List(); Func provider = _ => TimeSpan.Zero; @@ -238,7 +238,7 @@ public void Should_call_onretry_on_each_retry_with_the_current_retry_count() } [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() + public async Task Should_not_call_onretry_when_no_retries_are_performed() { Func provider = _ => 1.Seconds(); var retryExceptions = new List(); @@ -247,8 +247,8 @@ public void Should_not_call_onretry_when_no_retries_are_performed() .Handle() .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); retryExceptions.Should().BeEmpty(); } @@ -374,7 +374,7 @@ await policy.ExecuteAsync(async (context, _) => } [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() + public async Task Should_wait_asynchronously_for_async_onretry_delegate() { // This test relates to https://github.com/App-vNext/Polly/issues/107. // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx @@ -396,12 +396,12 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; }); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { executeDelegateInvocations++; await TaskHelper.EmptyTask; if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } - })).Should().NotThrow(); + })).Should().NotThrowAsync(); while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. @@ -410,7 +410,7 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() } [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { Func provider = _ => TimeSpan.Zero; @@ -429,14 +429,14 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca AttemptDuringWhichToCancel = null, }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { Func provider = _ => TimeSpan.Zero; @@ -458,15 +458,15 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex cancellationTokenSource.Cancel(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(0); } [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { Func provider = _ => TimeSpan.Zero; @@ -487,15 +487,15 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { Func provider = _ => TimeSpan.Zero; @@ -516,15 +516,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { Func provider = _ => TimeSpan.Zero; @@ -545,15 +545,15 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { Func provider = _ => TimeSpan.Zero; @@ -574,15 +574,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = true }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { Func provider = _ => TimeSpan.Zero; @@ -603,15 +603,15 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(2); } [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { Func provider = _ => TimeSpan.Zero; @@ -636,15 +636,15 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance ActionObservesCancellation = false }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); attemptsInvoked.Should().Be(1); } [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { Func provider = _ => TimeSpan.Zero; @@ -666,7 +666,7 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action).Should().NotThrow(); + await policy.Awaiting(action).Should().NotThrowAsync(); result.Should().BeTrue(); @@ -674,7 +674,7 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance } [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + public async Task Should_honour_and_report_cancellation_during_func_execution() { Func provider = _ => TimeSpan.Zero; @@ -698,8 +698,8 @@ public void Should_honour_and_report_cancellation_during_func_execution() }; Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); result.Should().Be(null); diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs index b7b795eaafe..cbd9a4752ea 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs @@ -201,7 +201,7 @@ public void Should_not_sleep_if_no_retries() [Fact] public void Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; var retryExceptions = new List(); Func provider = _ => TimeSpan.Zero; diff --git a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs index c95b002007e..022f45aa30b 100644 --- a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs @@ -210,14 +210,14 @@ public void Should_throw_when_exception_thrown_is_not_the_specified_exception_ty } [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() { var policy = Policy .Handle() .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] @@ -233,15 +233,15 @@ public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_excep } [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types_async() + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types_async() { var policy = Policy .Handle() .Or() .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); } [Fact] @@ -282,7 +282,7 @@ public void Should_not_throw_when_specified_exception_predicate_is_satisfied() } [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied_async() + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied_async() { var policy = Policy .Handle(_ => true) @@ -291,8 +291,8 @@ public void Should_not_throw_when_specified_exception_predicate_is_satisfied_asy 1.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] @@ -311,7 +311,7 @@ public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_ } [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() { var policy = Policy .Handle(_ => true) @@ -321,8 +321,8 @@ public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_ 1.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); } [Fact] @@ -440,7 +440,7 @@ public void Should_call_onretry_on_each_retry_with_the_current_timespan() [Fact] public void Should_call_onretry_on_each_retry_with_the_current_exception() { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; var retryExceptions = new List(); var policy = Policy @@ -1097,7 +1097,7 @@ public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandr attemptsInvoked.Should().Be(1); watch.Elapsed.Should().BeLessThan(retryDelay); - watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: (int)(shimTimeSpan.TotalMilliseconds) / 2); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. + watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: TimeSpan.FromMilliseconds((int)shimTimeSpan.TotalMilliseconds / 2)); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. } [Fact] diff --git a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs index a1923d69f7e..949a4196a5b 100644 --- a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs @@ -199,17 +199,17 @@ public void Should_be_able_to_configure_with_timeout_func() #region Timeout operation - pessimistic [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + public async Task Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { TimeSpan timeout = TimeSpan.FromMilliseconds(50); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - })).Should().Throw(); + })).Should().ThrowAsync(); } [Fact] @@ -224,12 +224,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); }; - act.Should().NotThrow(); + act.Should().NotThrowAsync(); result.Should().Be(ResultPrimitive.Good); } [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() + public async Task Should_throw_timeout_after_correct_duration__pessimistic() { Stopwatch watch = new Stopwatch(); @@ -239,23 +239,23 @@ public void Should_throw_timeout_after_correct_duration__pessimistic() TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); })) - .Should().Throw(); + .Should().ThrowAsync(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() + public async Task Should_rethrow_exception_from_inside_delegate__pessimistic() { var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); } #endregion @@ -264,19 +264,19 @@ public void Should_rethrow_exception_from_inside_delegate__pessimistic() #region Timeout operation - optimistic [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + public async Task Should_throw_when_timeout_is_less_than_execution_duration__optimistic() { TimeSpan timeout = TimeSpan.FromMilliseconds(50); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); } [Fact] @@ -294,12 +294,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op }, userCancellationToken); }; - act.Should().NotThrow(); + act.Should().NotThrowAsync(); result.Should().Be(ResultPrimitive.Good); } [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() + public async Task Should_throw_timeout_after_correct_duration__optimistic() { Stopwatch watch = new Stopwatch(); @@ -310,23 +310,22 @@ public void Should_throw_timeout_after_correct_duration__optimistic() TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); - }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() + public async Task Should_rethrow_exception_from_inside_delegate__optimistic() { var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); } #endregion @@ -334,26 +333,26 @@ public void Should_rethrow_exception_from_inside_delegate__optimistic() #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + public async Task Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { int timeout = 5; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { - policy.Awaiting(p => p.ExecuteAsync(async + await policy.Awaiting(p => p.ExecuteAsync(async _ => { userTokenSource.Cancel(); // User token cancels in the middle of execution ... await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), CancellationToken.None // ... but if the executed delegate does not observe it ); }, userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. + ).Should().ThrowAsync(); // ... it's still the timeout we expect. } } [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); @@ -363,12 +362,12 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(_ => + await policy.Awaiting(p => p.ExecuteAsync(_ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) - .Should().Throw(); + .Should().ThrowAsync(); } executed.Should().BeFalse(); @@ -379,24 +378,24 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + public async Task Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { int timeout = 10; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { - policy.Awaiting(p => p.ExecuteAsync( + await policy.Awaiting(p => p.ExecuteAsync( ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution return TaskHelper.EmptyTask; }, userTokenSource.Token) // ... with user token. - ).Should().Throw(); + ).Should().ThrowAsync(); } } [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); @@ -406,25 +405,25 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(_ => + await policy.Awaiting(p => p.ExecuteAsync(_ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) - .Should().Throw(); + .Should().ThrowAsync(); } executed.Should().BeFalse(); } [Fact] - public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() + public async Task Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() { var userException = new Exception(); var shimTimeSpan = TimeSpan.FromSeconds(0.2); var policy = Policy.TimeoutAsync(shimTimeSpan, TimeoutStrategy.Optimistic); - var thrown = policy.Awaiting(p => p.ExecuteAsync(async _ => + var thrown = await policy.Awaiting(p => p.ExecuteAsync(async _ => { try { @@ -443,13 +442,12 @@ public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeo }, CancellationToken.None)) .Should() - .Throw() - .Which; + .ThrowAsync(); - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().BeSameAs(userException); + thrown.NotBeOfType(); + thrown.NotBeOfType(); + thrown.NotBeOfType(); + thrown.Which.Should().BeSameAs(userException); } #endregion @@ -457,7 +455,7 @@ public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeo #region onTimeout overload - pessimistic [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() + public async Task Should_call_ontimeout_with_configured_timeout__pessimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -470,18 +468,17 @@ public void Should_call_ontimeout_with_configured_timeout__pessimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - })) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); } [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() + public async Task Should_call_ontimeout_with_passed_context__pessimistic() { string operationKey = "SomeKey"; Context contextPassedToExecute = new Context(operationKey); @@ -496,12 +493,11 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async _ => + await policy.Awaiting(p => p.ExecuteAsync(async _ => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - }, contextPassedToExecute)) - .Should().Throw(); + .Should().ThrowAsync(); contextPassedToOnTimeout.Should().NotBeNull(); contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); @@ -512,7 +508,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); @@ -525,11 +521,11 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); })) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); } @@ -538,7 +534,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) { Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; @@ -554,17 +550,17 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu // Supply a programatically-controlled timeout, via the execution context. Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - policy.Awaiting(p => p.ExecuteAsync(async _ => + await policy.Awaiting(p => p.ExecuteAsync(async _ => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); }, context)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); } [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() { Task taskPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, task) => @@ -576,11 +572,11 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimist TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); })) - .Should().Throw(); + .Should().ThrowAsync(); taskPassedToOnTimeout.Should().NotBeNull(); } @@ -605,12 +601,12 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); throw exceptionToThrow; })) - .Should().Throw(); + .Should().ThrowAsync(); await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); @@ -619,7 +615,7 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo } [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + public async Task Should_call_ontimeout_with_timing_out_exception__pessimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -632,12 +628,11 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - })) - .Should().Throw(); + .Should().ThrowAsync(); exceptionPassedToOnTimeout.Should().NotBeNull(); exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); @@ -648,7 +643,7 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() #region onTimeout overload - optimistic [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() + public async Task Should_call_ontimeout_with_configured_timeout__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -662,18 +657,17 @@ public void Should_call_ontimeout_with_configured_timeout__optimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); } [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() + public async Task Should_call_ontimeout_with_passed_context__optimistic() { string operationKey = "SomeKey"; Context contextPassedToExecute = new Context(operationKey); @@ -689,12 +683,11 @@ public void Should_call_ontimeout_with_passed_context__optimistic() var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, contextPassedToExecute, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); contextPassedToOnTimeout.Should().NotBeNull(); contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); @@ -705,7 +698,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); @@ -719,12 +712,11 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); } @@ -733,7 +725,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) { Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; @@ -753,18 +745,18 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); }, context, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); } [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + public async Task Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() { Task taskPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, task) => @@ -777,18 +769,17 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); taskPassedToOnTimeout.Should().BeNull(); } [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() + public async Task Should_call_ontimeout_with_timing_out_exception__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -802,12 +793,11 @@ public void Should_call_ontimeout_with_timing_out_exception__optimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); exceptionPassedToOnTimeout.Should().NotBeNull(); exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); diff --git a/src/Polly.Specs/Timeout/TimeoutSpecs.cs b/src/Polly.Specs/Timeout/TimeoutSpecs.cs index c8bc4aa1342..ba832b25c96 100644 --- a/src/Polly.Specs/Timeout/TimeoutSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutSpecs.cs @@ -260,7 +260,7 @@ public void Should_throw_timeout_after_correct_duration__pessimistic() .Should().Throw(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] @@ -401,7 +401,7 @@ public void Should_throw_timeout_after_correct_duration__optimistic() .Should().Throw(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] diff --git a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs index a178dbbfd1d..de3b85e60b4 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -200,17 +200,17 @@ public void Should_be_able_to_configure_with_timeout_func() #region Timeout operation - pessimistic [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + public async Task Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { TimeSpan timeout = TimeSpan.FromMilliseconds(50); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; - })).Should().Throw(); + })).Should().ThrowAsync(); } [Fact] @@ -222,12 +222,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe Func act = async () => result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - act.Should().NotThrow(); + act.Should().NotThrowAsync(); result.Should().Be(ResultPrimitive.Good); } [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() + public async Task Should_throw_timeout_after_correct_duration__pessimistic() { Stopwatch watch = new Stopwatch(); @@ -237,23 +237,23 @@ public void Should_throw_timeout_after_correct_duration__pessimistic() TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw(); + .Should().ThrowAsync(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() + public async Task Should_rethrow_exception_from_inside_delegate__pessimistic() { var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); } #endregion @@ -261,16 +261,16 @@ public void Should_rethrow_exception_from_inside_delegate__pessimistic() #region Timeout operation - optimistic [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + public async Task Should_throw_when_timeout_is_less_than_execution_duration__optimistic() { var policy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)).Should().Throw(); + }, userCancellationToken)).Should().ThrowAsync(); } [Fact] @@ -283,12 +283,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op Func act = async () => result = await policy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), userCancellationToken); - act.Should().NotThrow(); + act.Should().NotThrowAsync(); result.Should().Be(ResultPrimitive.Good); } [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() + public async Task Should_throw_timeout_after_correct_duration__optimistic() { Stopwatch watch = new Stopwatch(); @@ -299,23 +299,23 @@ public void Should_throw_timeout_after_correct_duration__optimistic() TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() + public async Task Should_rethrow_exception_from_inside_delegate__optimistic() { var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); } #endregion @@ -323,14 +323,14 @@ public void Should_rethrow_exception_from_inside_delegate__optimistic() #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + public async Task Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { int timeout = 5; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { - policy.Awaiting(p => p.ExecuteAsync(async + await policy.Awaiting(p => p.ExecuteAsync(async _ => { userTokenSource.Cancel(); // User token cancels in the middle of execution ... await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), @@ -338,12 +338,12 @@ await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), ); return ResultPrimitive.WhateverButTooLate; }, userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. + ).Should().ThrowAsync(); // ... it's still the timeout we expect. } } [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); @@ -353,13 +353,13 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(async _ => + await policy.Awaiting(p => p.ExecuteAsync(async _ => { executed = true; await TaskHelper.EmptyTask; return ResultPrimitive.WhateverButTooLate; }, cts.Token)) - .Should().Throw(); + .Should().ThrowAsync(); } executed.Should().BeFalse(); @@ -370,23 +370,23 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + public async Task Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { int timeout = 10; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { - policy.Awaiting(p => p.ExecuteAsync( + await policy.Awaiting(p => p.ExecuteAsync( ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution return Task.FromResult(ResultPrimitive.WhateverButTooLate); }, userTokenSource.Token) // ... with user token. - ).Should().Throw(); + ).Should().ThrowAsync(); } } [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); @@ -396,13 +396,13 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(async _ => + await policy.Awaiting(p => p.ExecuteAsync(async _ => { executed = true; await TaskHelper.EmptyTask; return ResultPrimitive.WhateverButTooLate; }, cts.Token)) - .Should().Throw(); + .Should().ThrowAsync(); } executed.Should().BeFalse(); @@ -413,7 +413,7 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled #region onTimeout overload - pessimistic [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() + public async Task Should_call_ontimeout_with_configured_timeout__pessimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -426,18 +426,18 @@ public void Should_call_ontimeout_with_configured_timeout__pessimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); } [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() + public async Task Should_call_ontimeout_with_passed_context__pessimistic() { string operationKey = "SomeKey"; Context contextPassedToExecute = new Context(operationKey); @@ -452,12 +452,12 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async _ => + await policy.Awaiting(p => p.ExecuteAsync(async _ => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; }, contextPassedToExecute)) - .Should().Throw(); + .Should().ThrowAsync(); contextPassedToOnTimeout.Should().NotBeNull(); contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); @@ -468,7 +468,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); @@ -481,12 +481,12 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); } @@ -495,7 +495,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) { Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; @@ -511,18 +511,18 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu // Supply a programatically-controlled timeout, via the execution context. Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - policy.Awaiting(p => p.ExecuteAsync(async _ => + await policy.Awaiting(p => p.ExecuteAsync(async _ => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; }, context)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); } [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() { Task taskPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, task) => @@ -534,12 +534,12 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimist TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw(); + .Should().ThrowAsync(); taskPassedToOnTimeout.Should().NotBeNull(); } @@ -564,12 +564,12 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); throw exceptionToThrow; })) - .Should().Throw(); + .Should().ThrowAsync(); await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); @@ -578,7 +578,7 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo } [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + public async Task Should_call_ontimeout_with_timing_out_exception__pessimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -591,12 +591,12 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw(); + .Should().ThrowAsync(); exceptionPassedToOnTimeout.Should().NotBeNull(); exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); @@ -607,7 +607,7 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() #region onTimeout overload - optimistic [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() + public async Task Should_call_ontimeout_with_configured_timeout__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -621,18 +621,18 @@ public void Should_call_ontimeout_with_configured_timeout__optimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); } [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() + public async Task Should_call_ontimeout_with_passed_context__optimistic() { string operationKey = "SomeKey"; Context contextPassedToExecute = new Context(operationKey); @@ -648,12 +648,12 @@ public void Should_call_ontimeout_with_passed_context__optimistic() var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, contextPassedToExecute, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); contextPassedToOnTimeout.Should().NotBeNull(); contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); @@ -664,7 +664,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); @@ -679,12 +679,12 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); } @@ -693,7 +693,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) { Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; @@ -713,18 +713,18 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, context, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); } [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + public async Task Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() { Task taskPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, task) => @@ -737,18 +737,18 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); taskPassedToOnTimeout.Should().BeNull(); } [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() + public async Task Should_call_ontimeout_with_timing_out_exception__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -762,12 +762,12 @@ public void Should_call_ontimeout_with_timing_out_exception__optimistic() var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) - .Should().Throw(); + .Should().ThrowAsync(); exceptionPassedToOnTimeout.Should().NotBeNull(); exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); diff --git a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs index 1cbf43fe6ad..ec887ff92db 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs @@ -251,7 +251,7 @@ public void Should_throw_timeout_after_correct_duration__pessimistic() .Should().Throw(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] @@ -402,7 +402,7 @@ public void Should_throw_timeout_after_correct_duration__optimistic() .Should().Throw(); watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); } [Fact] diff --git a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs index 5b0f9e85e91..dcd7d3f0493 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs @@ -360,7 +360,7 @@ public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set #region Instance-configured: execution tests [Fact] - public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + public async Task Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() { var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); @@ -370,14 +370,14 @@ public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_o // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. breaker.Reset(); - retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); + await retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. breaker.Reset(); - breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); + await breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } @@ -408,7 +408,7 @@ public async Task Wrapping_two_generic_policies_by_instance_syntax_and_executing #region Static-configured: execution tests [Fact] - public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + public async Task Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() { var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); @@ -418,14 +418,14 @@ public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_out // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. breaker.Reset(); - retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); + await retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. breaker.Reset(); - breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); + await breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Closed); } diff --git a/src/Polly/Polly.csproj b/src/Polly/Polly.csproj index 12e76e76815..d1b5712018a 100644 --- a/src/Polly/Polly.csproj +++ b/src/Polly/Polly.csproj @@ -1,4 +1,4 @@ - + netstandard1.1;netstandard2.0;net461;net472 From 3cbef2b97a93892fa579bf86e993307d03d227fa Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Sun, 13 Feb 2022 11:02:58 +0000 Subject: [PATCH 06/24] Add code coverage (#919) Add coverlet.msbuild to collect code coverage from the tests. --- .gitignore | 3 ++- src/Polly.Specs/Polly.Specs.csproj | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 08bc694c20c..6ef7d753859 100644 --- a/.gitignore +++ b/.gitignore @@ -123,4 +123,5 @@ tools *.lock.json *.nuget.targets -*.nuget.props \ No newline at end of file +*.nuget.props +coverage.*.json diff --git a/src/Polly.Specs/Polly.Specs.csproj b/src/Polly.Specs/Polly.Specs.csproj index b1d6577c94b..6f224b89033 100644 --- a/src/Polly.Specs/Polly.Specs.csproj +++ b/src/Polly.Specs/Polly.Specs.csproj @@ -12,6 +12,7 @@ + @@ -23,4 +24,11 @@ + + true + [xunit.*]* + System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute + 75,60,70 + + From 98f7429de1f9b2ba9a56442b80928d64c937129b Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 13 Feb 2022 10:54:18 +0000 Subject: [PATCH 07/24] Add Visual Studio Code config files Add default tasks for building and testing Polly with Visual Studio Code. --- .gitignore | 1 - .vscode/extensions.json | 7 +++++++ .vscode/launch.json | 19 +++++++++++++++++++ .vscode/tasks.json | 15 +++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index 6ef7d753859..444b9d4f606 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ TestResults *.user *.sln.docstates .vs/ -.vscode/ .idea/ .ionide/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000000..23f0bc802dd --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "EditorConfig.EditorConfig", + "k--kato.docomment", + "ms-dotnettools.csharp" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000..1333961f325 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run tests", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "dotnet", + "args": [ + "test" + ], + "cwd": "${workspaceFolder}/src/Polly.Specs", + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000000..395486b5a09 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet build ./src/Polly.sln", + "type": "shell", + "group": "build", + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + } + ] +} From c36712ecaf5af01dde00457d495408050fa3c037 Mon Sep 17 00:00:00 2001 From: Eugene Ogongo Date: Sun, 13 Feb 2022 16:43:44 +0300 Subject: [PATCH 08/24] Bump Cake to v2.0.0 (#921) * Bump up cake to v2.0.0 * Target the tools to a specific version --- .config/dotnet-tools.json | 12 ++++++++++++ build.cake | 29 +++++++++++++++-------------- build.ps1 | 13 +++++-------- 3 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 .config/dotnet-tools.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000000..31e896e989b --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "2.0.0", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file diff --git a/build.cake b/build.cake index a18ca7ce761..af25202dc10 100644 --- a/build.cake +++ b/build.cake @@ -9,16 +9,17 @@ var configuration = Argument("configuration", "Release"); // EXTERNAL NUGET TOOLS ////////////////////////////////////////////////////////////////////// -#Tool "xunit.runner.console" -#Tool "GitVersion.CommandLine" +#Tool "xunit.runner.console&version=2.4.1" +#Tool "GitVersion.CommandLine&version=5.8.1" ////////////////////////////////////////////////////////////////////// // EXTERNAL NUGET LIBRARIES ////////////////////////////////////////////////////////////////////// -#addin nuget:?package=Cake.FileHelpers&version=3.3.0 -#addin nuget:?package=Cake.Yaml&version=3.1.1 -#addin nuget:?package=YamlDotNet&version=5.2.1 +#addin nuget:?package=Cake.FileHelpers&version=5.0.0 +#addin nuget:?package=Cake.Yaml&version=4.0.0 +#addin nuget:?package=Newtonsoft.Json&version=13.0.1 +#addin nuget:?package=YamlDotNet&version=11.2.1 /////////////////////////////////////////////////////////////////////////////// // GLOBAL VARIABLES @@ -94,7 +95,7 @@ Task("__Clean") foreach(var path in solutionPaths) { Information("Cleaning {0}", path); - DotNetCoreClean(path.ToString()); + DotNetClean(path.ToString()); } }); @@ -104,7 +105,7 @@ Task("__RestoreNugetPackages") foreach(var solution in solutions) { Information("Restoring NuGet Packages for {0}", solution); - DotNetCoreRestore(solution.ToString()); + DotNetRestore(solution.ToString()); } }); @@ -201,14 +202,14 @@ Task("__BuildSolutions") { Information("Building {0}", solution); - var dotNetCoreBuildSettings = new DotNetCoreBuildSettings { + var dotNetCoreBuildSettings = new DotNetBuildSettings { Configuration = configuration, Verbosity = DotNetCoreVerbosity.Minimal, NoRestore = true, - MSBuildSettings = new DotNetCoreMSBuildSettings { TreatAllWarningsAs = MSBuildTreatAllWarningsAs.Error } + MSBuildSettings = new DotNetMSBuildSettings { TreatAllWarningsAs = MSBuildTreatAllWarningsAs.Error } }; - DotNetCoreBuild(solution.ToString(), dotNetCoreBuildSettings); + DotNetBuild(solution.ToString(), dotNetCoreBuildSettings); } }); @@ -216,7 +217,7 @@ Task("__RunTests") .Does(() => { foreach(var specsProj in GetFiles("./src/**/*.Specs.csproj")) { - DotNetCoreTest(specsProj.FullPath, new DotNetCoreTestSettings { + DotNetTest(specsProj.FullPath, new DotNetTestSettings { Configuration = configuration, NoBuild = true }); @@ -230,13 +231,13 @@ Task("__CreateSignedNugetPackage") Information("Building {0}.{1}.nupkg", packageName, nugetVersion); - var dotNetCorePackSettings = new DotNetCorePackSettings { + var dotNetCorePackSettings = new DotNetPackSettings { Configuration = configuration, NoBuild = true, OutputDirectory = nupkgDestDir }; - DotNetCorePack($@"{srcDir}\{projectName}.sln", dotNetCorePackSettings); + DotNetPack($@"{srcDir}\{projectName}.sln", dotNetCorePackSettings); }); ////////////////////////////////////////////////////////////////////// @@ -273,4 +274,4 @@ RunTarget(target); string ToolsExePath(string exeFileName) { var exePath = System.IO.Directory.GetFiles(@"./tools", exeFileName, SearchOption.AllDirectories).FirstOrDefault(); return exePath; -} +} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index 5d095556f4d..e27195feb14 100644 --- a/build.ps1 +++ b/build.ps1 @@ -51,8 +51,9 @@ if($Verbose.IsPresent) $TOOLS_DIR = Join-Path $PSScriptRoot "tools" $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" -$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$CAKE_EXE = "dotnet dotnet-cake" $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$DOTNET = "dotnet.exe" # Should we use mono? $UseMono = ""; @@ -111,7 +112,7 @@ if(-Not $SkipToolPackageRestore.IsPresent) # Install just Cake if missing config else { - $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install Cake -Version 0.38.5 -ExcludeVersion" # Pin Cake version to 0.38.5; see https://github.com/App-vNext/Polly/issues/416 + $NuGetOutput = Invoke-Expression "&`"$DOTNET`" tool install Cake.Tool --version 2.0.0" Write-Verbose ($NuGetOutput | Out-String) } Pop-Location @@ -121,12 +122,8 @@ if(-Not $SkipToolPackageRestore.IsPresent) } } -# Make sure that Cake has been installed. -if (!(Test-Path $CAKE_EXE)) { - Throw "Could not find Cake.exe" -} # Start Cake Write-Host "Running build script..." -Invoke-Expression "$CAKE_EXE `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental" -exit $LASTEXITCODE +Invoke-Expression "$CAKE_EXE `"$Script`" --target=`"$Target`" --configuration=`"$Configuration`" --verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental" +exit $LASTEXITCODE \ No newline at end of file From 9f2c129e157baaaaac596e5676771ef07cffd1fc Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 13 Feb 2022 14:00:25 +0000 Subject: [PATCH 09/24] Tidy up build scripts Do not edit the csproj outside CI. Use xplat path joining. Fix some casing. Make brace styles/spacing consistent. Add shebang for pwsh. Remove redundant variable. --- .config/dotnet-tools.json | 2 +- build.bat | 1 - build.cake | 70 ++++++++++++++++++++++----------------- build.ps1 | 17 +++++----- 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 31e896e989b..ed800ac9f70 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -9,4 +9,4 @@ ] } } -} \ No newline at end of file +} diff --git a/build.bat b/build.bat index 8dbd6710abd..dde0203b8be 100644 --- a/build.bat +++ b/build.bat @@ -3,4 +3,3 @@ PUSHD %~dp0 PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "& './build.ps1'" IF %errorlevel% neq 0 PAUSE - diff --git a/build.cake b/build.cake index af25202dc10..98920b3b91a 100644 --- a/build.cake +++ b/build.cake @@ -9,8 +9,8 @@ var configuration = Argument("configuration", "Release"); // EXTERNAL NUGET TOOLS ////////////////////////////////////////////////////////////////////// -#Tool "xunit.runner.console&version=2.4.1" #Tool "GitVersion.CommandLine&version=5.8.1" +#Tool "xunit.runner.console&version=2.4.1" ////////////////////////////////////////////////////////////////////// // EXTERNAL NUGET LIBRARIES @@ -32,15 +32,15 @@ var solutionPaths = solutions.Select(solution => solution.GetDirectory()); var srcDir = Directory("./src"); var artifactsDir = Directory("./artifacts"); -var testResultsDir = artifactsDir + Directory("test-results"); +var testResultsDir = System.IO.Path.Combine(artifactsDir, Directory("test-results")); // NuGet -var nupkgDestDir = artifactsDir + Directory("nuget-package"); +var nupkgDestDir = System.IO.Path.Combine(artifactsDir, Directory("nuget-package")); -// Gitversion +// GitVersion var gitVersionPath = ToolsExePath("GitVersion.exe"); -Dictionary gitVersionOutput; var gitVersionConfigFilePath = "./GitVersionConfig.yaml"; +Dictionary gitVersionOutput; // Versioning string nugetVersion; @@ -82,7 +82,8 @@ Teardown(_ => Task("__Clean") .Does(() => { - DirectoryPath[] cleanDirectories = new DirectoryPath[] { + DirectoryPath[] cleanDirectories = new DirectoryPath[] + { testResultsDir, nupkgDestDir, artifactsDir @@ -99,7 +100,7 @@ Task("__Clean") } }); -Task("__RestoreNugetPackages") +Task("__RestoreNuGetPackages") .Does(() => { foreach(var solution in solutions) @@ -115,7 +116,8 @@ Task("__UpdateAssemblyVersionInformation") var gitVersionSettings = new ProcessSettings() .SetRedirectStandardOutput(true); - try { + try + { IEnumerable outputLines; StartProcess(gitVersionPath, gitVersionSettings, out outputLines); @@ -130,13 +132,13 @@ Task("__UpdateAssemblyVersionInformation") GitVersionConfigYaml deserialized = DeserializeYaml(gitVersionYamlString.Replace("next-version", "NextVersion")); string gitVersionConfig = deserialized.NextVersion; - gitVersionOutput = new Dictionary{ + gitVersionOutput = new Dictionary + { { "NuGetVersion", gitVersionConfig + "-NotFromGitRepo" }, { "FullSemVer", gitVersionConfig }, { "AssemblySemVer", gitVersionConfig }, { "Major", gitVersionConfig.Split('.')[0] }, }; - } Information(""); @@ -152,19 +154,21 @@ Task("__UpdateAssemblyVersionInformation") Information(""); Information("Mapping versioning information to:"); - Information("Appveyor build number -> {0}", appveyorBuildNumber); - Information("Nuget package version -> {0}", nugetVersion); + Information("AppVeyor build number -> {0}", appveyorBuildNumber); + Information("NuGet package version -> {0}", nugetVersion); Information("AssemblyVersion -> {0}", assemblyVersion); Information("AssemblyFileVersion -> {0}", assemblySemver); Information("AssemblyInformationalVersion -> {0}", assemblySemver); }); Task("__UpdateDotNetStandardAssemblyVersionNumber") + .WithCriteria(() => AppVeyor.IsRunningOnAppVeyor) .Does(() => { Information("Updating Assembly Version Information"); - var attributeToValueMap = new Dictionary() { + var attributeToValueMap = new Dictionary() + { { "AssemblyVersion", assemblyVersion }, { "FileVersion", assemblySemver }, { "InformationalVersion", assemblySemver }, @@ -175,7 +179,8 @@ Task("__UpdateDotNetStandardAssemblyVersionNumber") var csproj = File("./src/" + projectName + "/" + projectName + ".csproj"); - foreach(var attributeMap in attributeToValueMap) { + foreach(var attributeMap in attributeToValueMap) + { var attribute = attributeMap.Key; var value = attributeMap.Value; @@ -185,7 +190,6 @@ Task("__UpdateDotNetStandardAssemblyVersionNumber") throw new Exception($"{attribute} version could not be updated in {csproj}."); } } - }); Task("__UpdateAppVeyorBuildNumber") @@ -202,11 +206,12 @@ Task("__BuildSolutions") { Information("Building {0}", solution); - var dotNetCoreBuildSettings = new DotNetBuildSettings { - Configuration = configuration, - Verbosity = DotNetCoreVerbosity.Minimal, - NoRestore = true, - MSBuildSettings = new DotNetMSBuildSettings { TreatAllWarningsAs = MSBuildTreatAllWarningsAs.Error } + var dotNetCoreBuildSettings = new DotNetBuildSettings + { + Configuration = configuration, + Verbosity = DotNetCoreVerbosity.Minimal, + NoRestore = true, + MSBuildSettings = new DotNetMSBuildSettings { TreatAllWarningsAs = MSBuildTreatAllWarningsAs.Error }, }; DotNetBuild(solution.ToString(), dotNetCoreBuildSettings); @@ -216,28 +221,31 @@ Task("__BuildSolutions") Task("__RunTests") .Does(() => { - foreach(var specsProj in GetFiles("./src/**/*.Specs.csproj")) { - DotNetTest(specsProj.FullPath, new DotNetTestSettings { + foreach(var specsProj in GetFiles("./src/**/*.Specs.csproj")) + { + DotNetTest(specsProj.FullPath, new DotNetTestSettings + { Configuration = configuration, - NoBuild = true + NoBuild = true, }); } }); -Task("__CreateSignedNugetPackage") +Task("__CreateSignedNuGetPackage") .Does(() => { var packageName = projectName; Information("Building {0}.{1}.nupkg", packageName, nugetVersion); - var dotNetCorePackSettings = new DotNetPackSettings { + var dotNetCorePackSettings = new DotNetPackSettings + { Configuration = configuration, NoBuild = true, - OutputDirectory = nupkgDestDir + OutputDirectory = nupkgDestDir, }; - DotNetPack($@"{srcDir}\{projectName}.sln", dotNetCorePackSettings); + DotNetPack(System.IO.Path.Combine(srcDir, projectName + ".sln"), dotNetCorePackSettings); }); ////////////////////////////////////////////////////////////////////// @@ -246,13 +254,13 @@ Task("__CreateSignedNugetPackage") Task("Build") .IsDependentOn("__Clean") - .IsDependentOn("__RestoreNugetPackages") + .IsDependentOn("__RestoreNuGetPackages") .IsDependentOn("__UpdateAssemblyVersionInformation") .IsDependentOn("__UpdateDotNetStandardAssemblyVersionNumber") .IsDependentOn("__UpdateAppVeyorBuildNumber") .IsDependentOn("__BuildSolutions") .IsDependentOn("__RunTests") - .IsDependentOn("__CreateSignedNugetPackage"); + .IsDependentOn("__CreateSignedNuGetPackage"); /////////////////////////////////////////////////////////////////////////////// // PRIMARY TARGETS @@ -272,6 +280,6 @@ RunTarget(target); ////////////////////////////////////////////////////////////////////// string ToolsExePath(string exeFileName) { - var exePath = System.IO.Directory.GetFiles(@"./tools", exeFileName, SearchOption.AllDirectories).FirstOrDefault(); + var exePath = System.IO.Directory.GetFiles("./tools", exeFileName, SearchOption.AllDirectories).FirstOrDefault(); return exePath; -} \ No newline at end of file +} diff --git a/build.ps1 b/build.ps1 index e27195feb14..0a1cda3f5c3 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,3 +1,4 @@ +#! /usr/bin/env pwsh <# .SYNOPSIS @@ -44,34 +45,33 @@ Param( Write-Host "Preparing to run build script..." # Should we show verbose messages? -if($Verbose.IsPresent) +if ($Verbose.IsPresent) { $VerbosePreference = "continue" } $TOOLS_DIR = Join-Path $PSScriptRoot "tools" $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" -$CAKE_EXE = "dotnet dotnet-cake" $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" $DOTNET = "dotnet.exe" # Should we use mono? $UseMono = ""; -if($Mono.IsPresent) { +if ($Mono.IsPresent) { Write-Verbose -Message "Using the Mono based scripting engine." $UseMono = "-mono" } # Should we use the new Roslyn? $UseExperimental = ""; -if($Experimental.IsPresent -and !($Mono.IsPresent)) { +if ($Experimental.IsPresent -and !($Mono.IsPresent)) { Write-Verbose -Message "Using experimental version of Roslyn." $UseExperimental = "-experimental" } # Is this a dry run? $UseDryRun = ""; -if($WhatIf.IsPresent) { +if ($WhatIf.IsPresent) { $UseDryRun = "-dryrun" } @@ -95,7 +95,7 @@ if (!(Test-Path $NUGET_EXE)) { $ENV:NUGET_EXE = $NUGET_EXE # Restore tools from NuGet? -if(-Not $SkipToolPackageRestore.IsPresent) +if (-Not $SkipToolPackageRestore.IsPresent) { # Restore tools from NuGet. Push-Location @@ -122,8 +122,7 @@ if(-Not $SkipToolPackageRestore.IsPresent) } } - # Start Cake Write-Host "Running build script..." -Invoke-Expression "$CAKE_EXE `"$Script`" --target=`"$Target`" --configuration=`"$Configuration`" --verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental" -exit $LASTEXITCODE \ No newline at end of file +Invoke-Expression "dotnet dotnet-cake `"$Script`" --target=`"$Target`" --configuration=`"$Configuration`" --verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental" +exit $LASTEXITCODE From 50b723ba3eddab2fc84b804fa1c552c603bd3d99 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 13 Feb 2022 14:02:47 +0000 Subject: [PATCH 10/24] Set executable bit Set the executable bit for macOS and Linux. --- build.ps1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build.ps1 diff --git a/build.ps1 b/build.ps1 old mode 100644 new mode 100755 From 9f8d3701b532ebc251c9e1264334f6eaf911d2ba Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 13 Feb 2022 14:05:28 +0000 Subject: [PATCH 11/24] Disable download progress Disable showing the download progress bar when downloading files. --- build.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.ps1 b/build.ps1 index 0a1cda3f5c3..6838e1e840c 100755 --- a/build.ps1 +++ b/build.ps1 @@ -42,6 +42,9 @@ Param( [switch]$Verbose ) +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + Write-Host "Preparing to run build script..." # Should we show verbose messages? From 475b7ba62972dffd83d3e3145a096c9c99c14138 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 13 Feb 2022 14:13:35 +0000 Subject: [PATCH 12/24] Skip net4xx tests on non-Windows Skip running the tests for .NET Framework on non-Windows operating systems. --- src/Polly.Specs/Polly.Specs.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Polly.Specs/Polly.Specs.csproj b/src/Polly.Specs/Polly.Specs.csproj index 6f224b89033..0ddbfaea1e7 100644 --- a/src/Polly.Specs/Polly.Specs.csproj +++ b/src/Polly.Specs/Polly.Specs.csproj @@ -1,7 +1,8 @@  - netcoreapp2.1;netcoreapp3.1;net6.0;net461;net472 + netcoreapp2.1;netcoreapp3.1;net6.0 + $(TargetFrameworks);net461;net472 false true From 47a2b5d2cddcbbcf44a8ec3c8cde73a477c9bccc Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 13 Feb 2022 14:14:03 +0000 Subject: [PATCH 13/24] Drop test target for netcoreapp2.1 .NET Core 2.1 is no longer supported. --- src/Polly.Specs/Polly.Specs.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Polly.Specs/Polly.Specs.csproj b/src/Polly.Specs/Polly.Specs.csproj index 0ddbfaea1e7..04a83c8fecc 100644 --- a/src/Polly.Specs/Polly.Specs.csproj +++ b/src/Polly.Specs/Polly.Specs.csproj @@ -1,10 +1,8 @@  - netcoreapp2.1;netcoreapp3.1;net6.0 + netcoreapp3.1;net6.0 $(TargetFrameworks);net461;net472 - - false true From 440955d27ba38618d83bc7437a892dd64a11b302 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 13 Feb 2022 14:17:55 +0000 Subject: [PATCH 14/24] Speed up CI Speed up CI by setting some additional environment variables. --- appveyor.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 8494dd7f5c7..6c4bbe3aa72 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,5 +12,7 @@ artifacts: - path: artifacts\nuget-package\*.snupkg environment: - # Skip dotnet package caching on build servers - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + DOTNET_NOLOGO: true + NUGET_XMLDOC_MODE: skip From 28ebb9460d8701f28918895199c76fb3d7b6718c Mon Sep 17 00:00:00 2001 From: Georgy Levchenko <55885862+FoxTes@users.noreply.github.com> Date: Mon, 14 Feb 2022 17:31:30 +0400 Subject: [PATCH 15/24] Fix NullReferenceException (#923) --- src/Polly/Retry/AsyncRetryEngine.cs | 4 ++-- src/Polly/Retry/RetryEngine.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Polly/Retry/AsyncRetryEngine.cs b/src/Polly/Retry/AsyncRetryEngine.cs index 56f167a2445..6a35f2ab267 100644 --- a/src/Polly/Retry/AsyncRetryEngine.cs +++ b/src/Polly/Retry/AsyncRetryEngine.cs @@ -41,7 +41,7 @@ internal static async Task ImplementationAsync( return result; } - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); if (!canRetry) { @@ -58,7 +58,7 @@ internal static async Task ImplementationAsync( throw; } - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); if (!canRetry) { diff --git a/src/Polly/Retry/RetryEngine.cs b/src/Polly/Retry/RetryEngine.cs index f61a2e396c7..d7e6703d831 100644 --- a/src/Polly/Retry/RetryEngine.cs +++ b/src/Polly/Retry/RetryEngine.cs @@ -39,7 +39,7 @@ internal static TResult Implementation( return result; } - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); if (!canRetry) { @@ -56,7 +56,7 @@ internal static TResult Implementation( throw; } - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); if (!canRetry) { From e32730600746b479db0241826b9bdef8eec9bb28 Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 14:06:33 +0000 Subject: [PATCH 16/24] Use file-scoped namespaces Use file-scoped namespaces to try and resolve the merge conflicts with the default branch. --- src/Polly.Benchmarks/Bulkhead.cs | 81 +- src/Polly.Benchmarks/Cache.cs | 145 +- src/Polly.Benchmarks/CircuitBreaker.cs | 51 +- src/Polly.Benchmarks/Fallback.cs | 51 +- src/Polly.Benchmarks/NoOp.cs | 51 +- src/Polly.Benchmarks/PolicyWrap.cs | 67 +- src/Polly.Benchmarks/PollyConfig.cs | 47 +- src/Polly.Benchmarks/RateLimit.cs | 51 +- src/Polly.Benchmarks/Retry.cs | 115 +- src/Polly.Benchmarks/Timeout.cs | 93 +- src/Polly.Benchmarks/Workloads.cs | 59 +- .../Bulkhead/BulkheadAsyncSpecs.cs | 131 +- src/Polly.Specs/Bulkhead/BulkheadScenario.cs | 43 +- src/Polly.Specs/Bulkhead/BulkheadScenarios.cs | 35 +- src/Polly.Specs/Bulkhead/BulkheadSpecs.cs | 127 +- src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs | 549 +- .../Bulkhead/BulkheadTResultAsyncSpecs.cs | 147 +- .../Bulkhead/BulkheadTResultSpecs.cs | 147 +- .../Bulkhead/IBulkheadPolicySpecs.cs | 27 +- src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs | 101 +- src/Polly.Specs/Caching/CacheAsyncSpecs.cs | 1079 ++-- src/Polly.Specs/Caching/CacheSpecs.cs | 1031 ++-- .../Caching/CacheTResultAsyncSpecs.cs | 763 ++- src/Polly.Specs/Caching/CacheTResultSpecs.cs | 733 ++- src/Polly.Specs/Caching/ContextualTtlSpecs.cs | 85 +- .../Caching/DefaultCacheKeyStrategySpecs.cs | 19 +- .../Caching/GenericCacheProviderAsyncSpecs.cs | 87 +- .../Caching/GenericCacheProviderSpecs.cs | 71 +- src/Polly.Specs/Caching/RelativeTtlSpecs.cs | 81 +- src/Polly.Specs/Caching/ResultTtlSpecs.cs | 91 +- .../SerializingCacheProviderAsyncSpecs.cs | 883 ++- .../Caching/SerializingCacheProviderSpecs.cs | 881 ++- src/Polly.Specs/Caching/SlidingTtlSpecs.cs | 57 +- .../AdvancedCircuitBreakerAsyncSpecs.cs | 5477 ++++++++--------- .../AdvancedCircuitBreakerSpecs.cs | 5387 ++++++++-------- .../CircuitBreakerAsyncSpecs.cs | 2607 ++++---- .../CircuitBreaker/CircuitBreakerSpecs.cs | 2641 ++++---- .../CircuitBreakerTResultAsyncSpecs.cs | 2449 ++++---- ...BreakerTResultMixedResultExceptionSpecs.cs | 1043 ++-- .../CircuitBreakerTResultSpecs.cs | 2429 ++++---- .../ICircuitBreakerPolicySpecs.cs | 77 +- .../ICircuitBreakerPolicyTResultSpecs.cs | 21 +- src/Polly.Specs/ContextSpecs.cs | 73 +- src/Polly.Specs/Custom/CustomAsyncSpecs.cs | 147 +- src/Polly.Specs/Custom/CustomSpecs.cs | 117 +- .../Custom/CustomTResultAsyncSpecs.cs | 171 +- src/Polly.Specs/Custom/CustomTResultSpecs.cs | 129 +- .../Fallback/FallbackAsyncSpecs.cs | 1183 ++-- src/Polly.Specs/Fallback/FallbackSpecs.cs | 1827 +++--- .../Fallback/FallbackTResultAsyncSpecs.cs | 1205 ++-- .../Fallback/FallbackTResultSpecs.cs | 1227 ++-- .../Helpers/Bulkhead/AnnotatedOutputHelper.cs | 79 +- .../Helpers/Bulkhead/AssertionFailure.cs | 25 +- .../Helpers/Bulkhead/SilentOutputHelper.cs | 19 +- .../Helpers/Bulkhead/TraceableAction.cs | 329 +- .../Helpers/Bulkhead/TraceableActionStatus.cs | 31 +- .../Helpers/Caching/StubCacheKeyStrategy.cs | 29 +- .../Helpers/Caching/StubCacheProvider.cs | 85 +- .../Caching/StubErroringCacheProvider.cs | 67 +- .../Helpers/Caching/StubSerialized.cs | 39 +- .../Helpers/Caching/StubSerializer.cs | 35 +- src/Polly.Specs/Helpers/Constants.cs | 27 +- .../Helpers/ContextualPolicyExtensions.cs | 53 +- .../ContextualPolicyExtensionsAsync.cs | 43 +- .../ContextualPolicyTResultExtensions.cs | 85 +- .../ContextualPolicyTResultExtensionsAsync.cs | 69 +- .../AddBehaviourIfHandleEngine.cs | 55 +- .../AddBehaviourIfHandlePolicy.cs | 87 +- .../AddBehaviourIfHandleSyntax.cs | 23 +- .../AsyncAddBehaviourIfHandleEngine.cs | 57 +- .../AsyncAddBehaviourIfHandlePolicy.cs | 99 +- .../AsyncAddBehaviourIfHandleSyntax.cs | 31 +- .../PreExecute/AsyncPreExecuteEngine.cs | 45 +- .../PreExecute/AsyncPreExecutePolicy.cs | 75 +- .../Custom/PreExecute/PreExecuteEngine.cs | 41 +- .../Custom/PreExecute/PreExecutePolicy.cs | 71 +- src/Polly.Specs/Helpers/ObjectExtensions.cs | 21 +- src/Polly.Specs/Helpers/PolicyExtensions.cs | 175 +- .../Helpers/PolicyExtensionsAsync.cs | 177 +- .../Helpers/PolicyTResultExtensions.cs | 161 +- .../Helpers/PolicyTResultExtensionsAsync.cs | 187 +- .../RateLimit/IRateLimiterExtensions.cs | 51 +- .../RateLimit/ResultClassWithRetryAfter.cs | 27 +- src/Polly.Specs/Helpers/ResultClass.cs | 31 +- src/Polly.Specs/Helpers/ResultPrimitive.cs | 29 +- .../IAsyncPolicyExtensionsSpecs.cs | 43 +- src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs | 43 +- src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs | 49 +- src/Polly.Specs/NoOp/NoOpSpecs.cs | 47 +- src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs | 55 +- src/Polly.Specs/NoOp/NoOpTResultSpecs.cs | 51 +- src/Polly.Specs/PolicyAsyncSpecs.cs | 493 +- .../PolicyContextAndKeyAsyncSpecs.cs | 479 +- src/Polly.Specs/PolicyContextAndKeySpecs.cs | 471 +- src/Polly.Specs/PolicySpecs.cs | 487 +- src/Polly.Specs/PolicyTResultAsyncSpecs.cs | 287 +- src/Polly.Specs/PolicyTResultSpecs.cs | 293 +- .../RateLimit/AsyncRateLimitPolicySpecs.cs | 57 +- .../AsyncRateLimitPolicyTResultSpecs.cs | 83 +- .../LockFreeTokenBucketRateLimiterTests.cs | 11 +- .../RateLimit/RateLimitPolicySpecs.cs | 57 +- .../RateLimit/RateLimitPolicySpecsBase.cs | 471 +- .../RateLimit/RateLimitPolicyTResultSpecs.cs | 83 +- .../RateLimitPolicyTResultSpecsBase.cs | 89 +- .../RateLimit/RateLimitSpecsBase.cs | 71 +- .../TokenBucketRateLimiterTestsBase.cs | 323 +- .../Registry/ConcurrentPolicyRegistrySpecs.cs | 423 +- .../Registry/PolicyRegistrySpecs.cs | 919 ++- .../Registry/ReadOnlyPolicyRegistrySpecs.cs | 471 +- src/Polly.Specs/Retry/RetryAsyncSpecs.cs | 1129 ++-- .../Retry/RetryForeverAsyncSpecs.cs | 787 ++- src/Polly.Specs/Retry/RetryForeverSpecs.cs | 491 +- src/Polly.Specs/Retry/RetrySpecs.cs | 1303 ++-- .../RetryTResultMixedResultExceptionSpecs.cs | 377 +- src/Polly.Specs/Retry/RetryTResultSpecs.cs | 1229 ++-- .../Retry/RetryTResultSpecsAsync.cs | 1203 ++-- .../Retry/WaitAndRetryAsyncSpecs.cs | 1715 +++--- .../Retry/WaitAndRetryForeverAsyncSpecs.cs | 1057 ++-- .../Retry/WaitAndRetryForeverSpecs.cs | 575 +- .../WaitAndRetryForeverTResultAsyncSpecs.cs | 85 +- .../Retry/WaitAndRetryForeverTResultSpecs.cs | 73 +- src/Polly.Specs/Retry/WaitAndRetrySpecs.cs | 1869 +++--- .../Retry/WaitAndRetryTResultAsyncSpecs.cs | 85 +- .../Retry/WaitAndRetryTResultSpecs.cs | 73 +- src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs | 1201 ++-- src/Polly.Specs/Timeout/TimeoutSpecs.cs | 1205 ++-- src/Polly.Specs/Timeout/TimeoutSpecsBase.cs | 153 +- .../Timeout/TimeoutTResultAsyncSpecs.cs | 1149 ++-- .../Timeout/TimeoutTResultSpecs.cs | 1223 ++-- .../Wrap/IPolicyWrapExtensionSpecs.cs | 511 +- .../Wrap/PolicyWrapContextAndKeySpecs.cs | 451 +- .../Wrap/PolicyWrapContextAndKeySpecsAsync.cs | 567 +- src/Polly.Specs/Wrap/PolicyWrapSpecs.cs | 955 ++- src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs | 959 ++- src/Polly/AsyncPolicy.ContextAndKeys.cs | 105 +- src/Polly/AsyncPolicy.ExecuteOverloads.cs | 913 ++- .../AsyncPolicy.GenericImplementation.cs | 35 +- .../AsyncPolicy.NonGenericImplementation.cs | 73 +- .../AsyncPolicy.TResult.ExecuteOverloads.cs | 461 +- src/Polly/AsyncPolicy.TResult.cs | 45 +- src/Polly/AsyncPolicy.cs | 37 +- src/Polly/Bulkhead/AsyncBulkheadEngine.cs | 53 +- src/Polly/Bulkhead/AsyncBulkheadPolicy.cs | 159 +- src/Polly/Bulkhead/AsyncBulkheadSyntax.cs | 127 +- .../Bulkhead/AsyncBulkheadTResultSyntax.cs | 123 +- src/Polly/Bulkhead/BulkheadEngine.cs | 49 +- src/Polly/Bulkhead/BulkheadPolicy.cs | 157 +- .../Bulkhead/BulkheadRejectedException.cs | 71 +- .../Bulkhead/BulkheadSemaphoreFactory.cs | 21 +- src/Polly/Bulkhead/BulkheadSyntax.cs | 123 +- src/Polly/Bulkhead/BulkheadTResultSyntax.cs | 123 +- src/Polly/Bulkhead/IBulkheadPolicy.cs | 39 +- src/Polly/Caching/AbsoluteTtl.cs | 19 +- src/Polly/Caching/AsyncCacheEngine.cs | 109 +- src/Polly/Caching/AsyncCachePolicy.cs | 227 +- src/Polly/Caching/AsyncCacheSyntax.cs | 609 +- src/Polly/Caching/AsyncCacheTResultSyntax.cs | 1631 +++-- .../Caching/AsyncGenericCacheProvider.cs | 35 +- .../Caching/AsyncSerializingCacheProvider.cs | 215 +- src/Polly/Caching/CacheEngine.cs | 107 +- src/Polly/Caching/CachePolicy.cs | 203 +- src/Polly/Caching/CacheProviderExtensions.cs | 125 +- src/Polly/Caching/CacheSyntax.cs | 633 +- src/Polly/Caching/CacheTResultSyntax.cs | 1641 +++-- src/Polly/Caching/ContextualTtl.cs | 63 +- src/Polly/Caching/DefaultCacheKeyStrategy.cs | 29 +- src/Polly/Caching/GenericCacheProvider.cs | 35 +- src/Polly/Caching/GenericTtlStrategy.cs | 33 +- src/Polly/Caching/IAsyncCacheProvider.cs | 103 +- src/Polly/Caching/ICacheItemSerializer.cs | 37 +- src/Polly/Caching/ICacheKeyStrategy.cs | 21 +- src/Polly/Caching/ICachePolicy.cs | 27 +- src/Polly/Caching/ISyncCacheProvider.cs | 83 +- src/Polly/Caching/ITtlStrategy.cs | 37 +- src/Polly/Caching/NonSlidingTtl.cs | 51 +- src/Polly/Caching/RelativeTtl.cs | 43 +- src/Polly/Caching/ResultTtl.cs | 57 +- src/Polly/Caching/SerializingCacheProvider.cs | 169 +- src/Polly/Caching/SlidingTtl.cs | 45 +- src/Polly/Caching/Ttl.cs | 59 +- src/Polly/Caching/TtlStrategyExtensions.cs | 25 +- .../AdvancedCircuitBreakerSyntax.cs | 463 +- .../AdvancedCircuitBreakerTResultSyntax.cs | 467 +- .../AdvancedCircuitController.cs | 155 +- .../AsyncAdvancedCircuitBreakerSyntax.cs | 471 +- ...syncAdvancedCircuitBreakerTResultSyntax.cs | 471 +- .../AsyncCircuitBreakerEngine.cs | 69 +- .../AsyncCircuitBreakerPolicy.cs | 205 +- .../AsyncCircuitBreakerSyntax.cs | 397 +- .../AsyncCircuitBreakerTResultSyntax.cs | 397 +- .../CircuitBreaker/BrokenCircuitException.cs | 149 +- .../CircuitBreaker/CircuitBreakerEngine.cs | 77 +- .../CircuitBreaker/CircuitBreakerPolicy.cs | 189 +- .../CircuitBreaker/CircuitBreakerSyntax.cs | 401 +- .../CircuitBreakerTResultSyntax.cs | 401 +- src/Polly/CircuitBreaker/CircuitState.cs | 41 +- .../CircuitBreaker/CircuitStateController.cs | 255 +- .../ConsecutiveCountCircuitController.cs | 115 +- src/Polly/CircuitBreaker/HealthCount.cs | 15 +- .../CircuitBreaker/ICircuitBreakerPolicy.cs | 61 +- .../CircuitBreaker/ICircuitController.cs | 25 +- src/Polly/CircuitBreaker/IHealthMetrics.cs | 15 +- .../IsolatedCircuitException.cs | 45 +- .../CircuitBreaker/RollingHealthMetrics.cs | 105 +- .../CircuitBreaker/SingleHealthMetrics.cs | 55 +- src/Polly/Context.Dictionary.cs | 187 +- src/Polly/Context.cs | 87 +- src/Polly/DelegateResult.cs | 45 +- src/Polly/ExceptionPredicate.cs | 17 +- src/Polly/ExceptionPredicates.cs | 49 +- src/Polly/ExecutionRejectedException.cs | 71 +- src/Polly/Fallback/AsyncFallbackEngine.cs | 67 +- src/Polly/Fallback/AsyncFallbackPolicy.cs | 145 +- src/Polly/Fallback/AsyncFallbackSyntax.cs | 403 +- src/Polly/Fallback/FallbackEngine.cs | 65 +- src/Polly/Fallback/FallbackPolicy.cs | 119 +- src/Polly/Fallback/FallbackSyntax.cs | 557 +- src/Polly/Fallback/IFallbackPolicy.cs | 27 +- src/Polly/IAsyncPolicy.Extensions.cs | 27 +- src/Polly/IAsyncPolicy.TResult.cs | 353 +- src/Polly/IAsyncPolicy.cs | 681 +- src/Polly/ISyncPolicy.Extensions.cs | 27 +- src/Polly/ISyncPolicy.TResult.cs | 221 +- src/Polly/ISyncPolicy.cs | 449 +- src/Polly/IsPolicy.cs | 19 +- src/Polly/NoOp/AsyncNoOpPolicy.cs | 55 +- src/Polly/NoOp/AsyncNoOpSyntax.cs | 17 +- src/Polly/NoOp/AsyncNoOpTResultSyntax.cs | 19 +- src/Polly/NoOp/INoOpPolicy.cs | 27 +- src/Polly/NoOp/NoOpEngine.cs | 11 +- src/Polly/NoOp/NoOpEngineAsync.cs | 11 +- src/Polly/NoOp/NoOpPolicy.cs | 51 +- src/Polly/NoOp/NoOpSyntax.cs | 17 +- src/Polly/NoOp/NoOpTResultSyntax.cs | 19 +- src/Polly/Policy.ContextAndKeys.cs | 105 +- src/Polly/Policy.ExecuteOverloads.cs | 627 +- src/Polly/Policy.HandleSyntax.cs | 193 +- src/Polly/Policy.SyncGenericImplementation.cs | 31 +- .../Policy.SyncNonGenericImplementation.cs | 43 +- src/Polly/Policy.TResult.ExecuteOverloads.cs | 329 +- src/Polly/Policy.TResult.cs | 39 +- src/Polly/Policy.cs | 39 +- src/Polly/PolicyBase.ContextAndKeys.cs | 71 +- src/Polly/PolicyBase.cs | 123 +- src/Polly/PolicyBuilder.OrSyntax.cs | 327 +- src/Polly/PolicyBuilder.cs | 269 +- src/Polly/PolicyResult.cs | 377 +- src/Polly/RateLimit/AsyncRateLimitEngine.cs | 43 +- src/Polly/RateLimit/AsyncRateLimitPolicy.cs | 73 +- src/Polly/RateLimit/AsyncRateLimitSyntax.cs | 75 +- .../RateLimit/AsyncRateLimitTResultSyntax.cs | 151 +- src/Polly/RateLimit/IRateLimitPolicy.cs | 25 +- src/Polly/RateLimit/IRateLimiter.cs | 19 +- .../LockFreeTokenBucketRateLimiter.cs | 185 +- src/Polly/RateLimit/RateLimitEngine.cs | 43 +- src/Polly/RateLimit/RateLimitPolicy.cs | 69 +- .../RateLimit/RateLimitRejectedException.cs | 121 +- src/Polly/RateLimit/RateLimitSyntax.cs | 75 +- src/Polly/RateLimit/RateLimitTResultSyntax.cs | 151 +- src/Polly/RateLimit/RateLimiterFactory.cs | 11 +- .../Registry/IConcurrentPolicyRegistry.cs | 155 +- src/Polly/Registry/IPolicyRegistry.cs | 69 +- src/Polly/Registry/IReadOnlyPolicyRegistry.cs | 91 +- src/Polly/Registry/PolicyRegistry.cs | 467 +- src/Polly/ResultPredicate.cs | 19 +- src/Polly/ResultPredicates.cs | 55 +- src/Polly/Retry/AsyncRetryEngine.cs | 123 +- src/Polly/Retry/AsyncRetryPolicy.cs | 165 +- src/Polly/Retry/AsyncRetrySyntax.cs | 2111 ++++--- src/Polly/Retry/AsyncRetryTResultSyntax.cs | 2109 ++++--- src/Polly/Retry/IRetryPolicy.cs | 27 +- src/Polly/Retry/RetryEngine.cs | 125 +- src/Polly/Retry/RetryPolicy.cs | 151 +- src/Polly/Retry/RetrySyntax.cs | 1213 ++-- src/Polly/Retry/RetryTResultSyntax.cs | 1317 ++-- src/Polly/Timeout/AsyncTimeoutEngine.cs | 103 +- src/Polly/Timeout/AsyncTimeoutPolicy.cs | 139 +- src/Polly/Timeout/AsyncTimeoutSyntax.cs | 763 ++- .../Timeout/AsyncTimeoutTResultSyntax.cs | 751 ++- src/Polly/Timeout/ITimeoutPolicy.cs | 27 +- src/Polly/Timeout/TimeoutEngine.cs | 97 +- src/Polly/Timeout/TimeoutPolicy.cs | 111 +- src/Polly/Timeout/TimeoutRejectedException.cs | 71 +- src/Polly/Timeout/TimeoutStrategy.cs | 25 +- src/Polly/Timeout/TimeoutSyntax.cs | 753 ++- src/Polly/Timeout/TimeoutTResultSyntax.cs | 767 ++- src/Polly/Timeout/TimeoutValidator.cs | 25 +- src/Polly/Utilities/EmptyStruct.cs | 15 +- src/Polly/Utilities/ExceptionExtensions.cs | 25 +- src/Polly/Utilities/KeyHelper.cs | 9 +- src/Polly/Utilities/SystemClock.cs | 91 +- src/Polly/Utilities/TaskHelper.cs | 23 +- src/Polly/Utilities/TimedLock.cs | 127 +- .../Wrap/AsyncPolicyWrap.ContextAndKeys.cs | 57 +- src/Polly/Wrap/AsyncPolicyWrap.cs | 285 +- src/Polly/Wrap/AsyncPolicyWrapEngine.cs | 177 +- src/Polly/Wrap/AsyncPolicyWrapSyntax.cs | 279 +- src/Polly/Wrap/IPolicyWrap.cs | 35 +- src/Polly/Wrap/IPolicyWrapExtension.cs | 123 +- src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs | 57 +- src/Polly/Wrap/PolicyWrap.cs | 261 +- src/Polly/Wrap/PolicyWrapEngine.cs | 77 +- src/Polly/Wrap/PolicyWrapSyntax.cs | 279 +- 303 files changed, 50030 insertions(+), 50333 deletions(-) diff --git a/src/Polly.Benchmarks/Bulkhead.cs b/src/Polly.Benchmarks/Bulkhead.cs index 275d39a1681..36a9449c838 100644 --- a/src/Polly.Benchmarks/Bulkhead.cs +++ b/src/Polly.Benchmarks/Bulkhead.cs @@ -2,48 +2,47 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class Bulkhead { - [Config(typeof(PollyConfig))] - public class Bulkhead + private static readonly Policy SyncPolicy = Policy.Bulkhead(2); + private static readonly AsyncPolicy AsyncPolicy = Policy.BulkheadAsync(2); + + [Benchmark] + public void Bulkhead_Synchronous() + { + SyncPolicy.Execute(() => Workloads.Action()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous_With_CancellationToken() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } + + [Benchmark] + public int Bulkhead_Synchronous_With_Result() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous_With_Result() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous_With_Result_With_CancellationToken() { - private static readonly Policy SyncPolicy = Policy.Bulkhead(2); - private static readonly AsyncPolicy AsyncPolicy = Policy.BulkheadAsync(2); - - [Benchmark] - public void Bulkhead_Synchronous() - { - SyncPolicy.Execute(() => Workloads.Action()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous() - { - await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous_With_CancellationToken() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } - - [Benchmark] - public int Bulkhead_Synchronous_With_Result() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous_With_Result() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous_With_Result_With_CancellationToken() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); } } diff --git a/src/Polly.Benchmarks/Cache.cs b/src/Polly.Benchmarks/Cache.cs index 558fbdd0d2a..c800251cddb 100644 --- a/src/Polly.Benchmarks/Cache.cs +++ b/src/Polly.Benchmarks/Cache.cs @@ -5,107 +5,106 @@ using Microsoft.Extensions.Caching.Memory; using Polly.Caching; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class Cache { - [Config(typeof(PollyConfig))] - public class Cache + private static readonly MemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions()); + private static readonly MemoryCacheProvider CacheProvider = new MemoryCacheProvider(MemoryCache); + + private static readonly Policy SyncPolicyMiss = Policy.Cache(CacheProvider, TimeSpan.Zero); + private static readonly AsyncPolicy AsyncPolicyMiss = Policy.CacheAsync(CacheProvider, TimeSpan.Zero); + + private static readonly Policy SyncPolicyHit = Policy.Cache(CacheProvider, TimeSpan.MaxValue); + private static readonly AsyncPolicy AsyncPolicyHit = Policy.CacheAsync(CacheProvider, TimeSpan.MaxValue); + + private static readonly Context HitContext = new Context(nameof(HitContext)); + private static readonly Context MissContext = new Context(nameof(MissContext)); + + [GlobalSetup] + public async Task GlobalSetup() { - private static readonly MemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions()); - private static readonly MemoryCacheProvider CacheProvider = new MemoryCacheProvider(MemoryCache); + SyncPolicyHit.Execute((context) => GetObject(), HitContext); + await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); + } - private static readonly Policy SyncPolicyMiss = Policy.Cache(CacheProvider, TimeSpan.Zero); - private static readonly AsyncPolicy AsyncPolicyMiss = Policy.CacheAsync(CacheProvider, TimeSpan.Zero); + [Benchmark] + public object Cache_Synchronous_Hit() + { + return SyncPolicyHit.Execute((context) => GetObject(), HitContext); + } - private static readonly Policy SyncPolicyHit = Policy.Cache(CacheProvider, TimeSpan.MaxValue); - private static readonly AsyncPolicy AsyncPolicyHit = Policy.CacheAsync(CacheProvider, TimeSpan.MaxValue); + [Benchmark] + public async Task Cache_Asynchronous_Hit() + { + return await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); + } - private static readonly Context HitContext = new Context(nameof(HitContext)); - private static readonly Context MissContext = new Context(nameof(MissContext)); + [Benchmark] + public object Cache_Synchronous_Miss() + { + return SyncPolicyMiss.Execute((context) => GetObject(), MissContext); + } - [GlobalSetup] - public async Task GlobalSetup() - { - SyncPolicyHit.Execute((context) => GetObject(), HitContext); - await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); - } + [Benchmark] + public async Task Cache_Asynchronous_Miss() + { + return await AsyncPolicyMiss.ExecuteAsync((context, token) => GetObjectAsync(token), MissContext, CancellationToken.None); + } - [Benchmark] - public object Cache_Synchronous_Hit() - { - return SyncPolicyHit.Execute((context) => GetObject(), HitContext); - } + private static object GetObject() => new object(); - [Benchmark] - public async Task Cache_Asynchronous_Hit() - { - return await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); - } + private static Task GetObjectAsync(CancellationToken cancellationToken) => Task.FromResult(new object()); - [Benchmark] - public object Cache_Synchronous_Miss() + private sealed class MemoryCacheProvider : ISyncCacheProvider, IAsyncCacheProvider + { + private readonly IMemoryCache _cache; + + public MemoryCacheProvider(IMemoryCache memoryCache) { - return SyncPolicyMiss.Execute((context) => GetObject(), MissContext); + _cache = memoryCache; } - [Benchmark] - public async Task Cache_Asynchronous_Miss() + public (bool, object) TryGet(string key) { - return await AsyncPolicyMiss.ExecuteAsync((context, token) => GetObjectAsync(token), MissContext, CancellationToken.None); + bool cacheHit = _cache.TryGetValue(key, out var value); + return (cacheHit, value); } - private static object GetObject() => new object(); - - private static Task GetObjectAsync(CancellationToken cancellationToken) => Task.FromResult(new object()); - - private sealed class MemoryCacheProvider : ISyncCacheProvider, IAsyncCacheProvider + public void Put(string key, object value, Ttl ttl) { - private readonly IMemoryCache _cache; - - public MemoryCacheProvider(IMemoryCache memoryCache) - { - _cache = memoryCache; - } + TimeSpan remaining = DateTimeOffset.MaxValue - DateTimeOffset.UtcNow; + var options = new MemoryCacheEntryOptions(); - public (bool, object) TryGet(string key) + if (ttl.SlidingExpiration) { - bool cacheHit = _cache.TryGetValue(key, out var value); - return (cacheHit, value); + options.SlidingExpiration = ttl.Timespan < remaining ? ttl.Timespan : remaining; } - - public void Put(string key, object value, Ttl ttl) + else { - TimeSpan remaining = DateTimeOffset.MaxValue - DateTimeOffset.UtcNow; - var options = new MemoryCacheEntryOptions(); - - if (ttl.SlidingExpiration) + if (ttl.Timespan == TimeSpan.MaxValue) { - options.SlidingExpiration = ttl.Timespan < remaining ? ttl.Timespan : remaining; + options.AbsoluteExpiration = DateTimeOffset.MaxValue; } else { - if (ttl.Timespan == TimeSpan.MaxValue) - { - options.AbsoluteExpiration = DateTimeOffset.MaxValue; - } - else - { - options.AbsoluteExpirationRelativeToNow = ttl.Timespan < remaining ? ttl.Timespan : remaining; - } + options.AbsoluteExpirationRelativeToNow = ttl.Timespan < remaining ? ttl.Timespan : remaining; } - - _cache.Set(key, value, options); } - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return Task.FromResult(TryGet(key)); - } + _cache.Set(key, value, options); + } - public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - Put(key, value, ttl); - return Task.CompletedTask; - } + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } + + public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + Put(key, value, ttl); + return Task.CompletedTask; } } } diff --git a/src/Polly.Benchmarks/CircuitBreaker.cs b/src/Polly.Benchmarks/CircuitBreaker.cs index c439e948043..b07e9c4d344 100644 --- a/src/Polly.Benchmarks/CircuitBreaker.cs +++ b/src/Polly.Benchmarks/CircuitBreaker.cs @@ -3,36 +3,35 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class CircuitBreaker { - [Config(typeof(PollyConfig))] - public class CircuitBreaker - { - private static readonly Policy SyncPolicy = Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)); - private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + private static readonly Policy SyncPolicy = Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)); + private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Benchmark] - public void CircuitBreaker_Synchronous_Succeeds() - { - SyncPolicy.Execute(() => Workloads.Action()); - } + [Benchmark] + public void CircuitBreaker_Synchronous_Succeeds() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public async Task CircuitBreaker_Asynchronous_Succeeds() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task CircuitBreaker_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public int CircuitBreaker_Synchronous_With_Result_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public int CircuitBreaker_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public async Task CircuitBreaker_Asynchronous_With_Result_Succeeds() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task CircuitBreaker_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); } } diff --git a/src/Polly.Benchmarks/Fallback.cs b/src/Polly.Benchmarks/Fallback.cs index 09395ebb0f1..7b602777c0e 100644 --- a/src/Polly.Benchmarks/Fallback.cs +++ b/src/Polly.Benchmarks/Fallback.cs @@ -2,36 +2,35 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class Fallback { - [Config(typeof(PollyConfig))] - public class Fallback - { - private static readonly Policy SyncPolicy = Policy.Handle().Fallback(0); - private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().FallbackAsync(0); + private static readonly Policy SyncPolicy = Policy.Handle().Fallback(0); + private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().FallbackAsync(0); - [Benchmark] - public int Fallback_Synchronous_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public int Fallback_Synchronous_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public async Task Fallback_Asynchronous_Succeeds() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } + [Benchmark] + public async Task Fallback_Asynchronous_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } - [Benchmark] - public int Fallback_Synchronous_Throws() - { - return SyncPolicy.Execute(() => Workloads.FuncThrows()); - } + [Benchmark] + public int Fallback_Synchronous_Throws() + { + return SyncPolicy.Execute(() => Workloads.FuncThrows()); + } - [Benchmark] - public async Task Fallback_Asynchronous_Throws() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncThrowsAsync()); - } + [Benchmark] + public async Task Fallback_Asynchronous_Throws() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncThrowsAsync()); } } diff --git a/src/Polly.Benchmarks/NoOp.cs b/src/Polly.Benchmarks/NoOp.cs index eb5ab880bdd..4e8666ae9e4 100644 --- a/src/Polly.Benchmarks/NoOp.cs +++ b/src/Polly.Benchmarks/NoOp.cs @@ -2,36 +2,35 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class NoOp { - [Config(typeof(PollyConfig))] - public class NoOp - { - private static readonly Policy SyncPolicy = Policy.NoOp(); - private static readonly AsyncPolicy AsyncPolicy = Policy.NoOpAsync(); + private static readonly Policy SyncPolicy = Policy.NoOp(); + private static readonly AsyncPolicy AsyncPolicy = Policy.NoOpAsync(); - [Benchmark] - public void NoOp_Synchronous() - { - SyncPolicy.Execute(() => Workloads.Action()); - } + [Benchmark] + public void NoOp_Synchronous() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public async Task NoOp_Asynchronous() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task NoOp_Asynchronous() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public int NoOp_Synchronous_With_Result() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public int NoOp_Synchronous_With_Result() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public async Task NoOp_Asynchronous_With_Result() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task NoOp_Asynchronous_With_Result() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); } } diff --git a/src/Polly.Benchmarks/PolicyWrap.cs b/src/Polly.Benchmarks/PolicyWrap.cs index 6d4280231f4..9b8838ea65d 100644 --- a/src/Polly.Benchmarks/PolicyWrap.cs +++ b/src/Polly.Benchmarks/PolicyWrap.cs @@ -3,45 +3,44 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class PolicyWrap { - [Config(typeof(PollyConfig))] - public class PolicyWrap - { - private static readonly Policy SyncPolicy = Policy.Wrap( - Policy.Handle().Retry(), - Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)), - Policy.Timeout(TimeSpan.FromMilliseconds(10)), - Policy.Bulkhead(2)); + private static readonly Policy SyncPolicy = Policy.Wrap( + Policy.Handle().Retry(), + Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)), + Policy.Timeout(TimeSpan.FromMilliseconds(10)), + Policy.Bulkhead(2)); - private static readonly AsyncPolicy AsyncPolicy = Policy.WrapAsync( - Policy.Handle().RetryAsync(), - Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)), - Policy.TimeoutAsync(TimeSpan.FromMilliseconds(10)), - Policy.BulkheadAsync(2)); + private static readonly AsyncPolicy AsyncPolicy = Policy.WrapAsync( + Policy.Handle().RetryAsync(), + Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)), + Policy.TimeoutAsync(TimeSpan.FromMilliseconds(10)), + Policy.BulkheadAsync(2)); - [Benchmark] - public void PolicyWrap_Synchronous() - { - SyncPolicy.Execute(() => Workloads.Action()); - } + [Benchmark] + public void PolicyWrap_Synchronous() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public async Task PolicyWrap_Asynchronous() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task PolicyWrap_Asynchronous() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public int PolicyWrap_Synchronous_With_Result() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public int PolicyWrap_Synchronous_With_Result() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public async Task PolicyWrap_Asynchronous_With_Result() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task PolicyWrap_Asynchronous_With_Result() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); } } diff --git a/src/Polly.Benchmarks/PollyConfig.cs b/src/Polly.Benchmarks/PollyConfig.cs index cad9e4148df..03218e70c24 100644 --- a/src/Polly.Benchmarks/PollyConfig.cs +++ b/src/Polly.Benchmarks/PollyConfig.cs @@ -1,37 +1,36 @@ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +internal class PollyConfig : ManualConfig { - internal class PollyConfig : ManualConfig + public PollyConfig() { - public PollyConfig() - { - var job = Job.Default; + var job = Job.Default; - AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default); + AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default); - AddJob(PollyJob(job, useNuGet: true).AsBaseline()); - AddJob(PollyJob(job, useNuGet: false)); - } - - private static Job PollyJob(Job job, bool useNuGet) - { - var result = job - .WithId("Polly" + (useNuGet ? string.Empty : "-dev")) - .WithArguments( - new[] - { - new MsBuildArgument("/p:BenchmarkFromNuGet=" + useNuGet), - new MsBuildArgument("/p:SignAssembly=false"), - }); + AddJob(PollyJob(job, useNuGet: true).AsBaseline()); + AddJob(PollyJob(job, useNuGet: false)); + } - if (useNuGet) + private static Job PollyJob(Job job, bool useNuGet) + { + var result = job + .WithId("Polly" + (useNuGet ? string.Empty : "-dev")) + .WithArguments( + new[] { - result = result.WithNuGet("Polly", "7.2.3"); - } + new MsBuildArgument("/p:BenchmarkFromNuGet=" + useNuGet), + new MsBuildArgument("/p:SignAssembly=false"), + }); - return result; + if (useNuGet) + { + result = result.WithNuGet("Polly", "7.2.3"); } + + return result; } } diff --git a/src/Polly.Benchmarks/RateLimit.cs b/src/Polly.Benchmarks/RateLimit.cs index e883a5e9ba5..c4267856168 100644 --- a/src/Polly.Benchmarks/RateLimit.cs +++ b/src/Polly.Benchmarks/RateLimit.cs @@ -2,36 +2,35 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class RateLimit { - [Config(typeof(PollyConfig))] - public class RateLimit - { - private static readonly Policy SyncPolicy = Policy.RateLimit(20, TimeSpan.FromSeconds(1), int.MaxValue); - private static readonly AsyncPolicy AsyncPolicy = Policy.RateLimitAsync(20, TimeSpan.FromSeconds(1), int.MaxValue); + private static readonly Policy SyncPolicy = Policy.RateLimit(20, TimeSpan.FromSeconds(1), int.MaxValue); + private static readonly AsyncPolicy AsyncPolicy = Policy.RateLimitAsync(20, TimeSpan.FromSeconds(1), int.MaxValue); - [Benchmark] - public void RateLimit_Synchronous_Succeeds() - { - SyncPolicy.Execute(() => Workloads.Action()); - } + [Benchmark] + public void RateLimit_Synchronous_Succeeds() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public async Task RateLimit_Asynchronous_Succeeds() - { - await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); - } + [Benchmark] + public async Task RateLimit_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } - [Benchmark] - public int RateLimit_Synchronous_With_Result_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public int RateLimit_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public async Task RateLimit_Asynchronous_With_Result_Succeeds() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } + [Benchmark] + public async Task RateLimit_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); } } diff --git a/src/Polly.Benchmarks/Retry.cs b/src/Polly.Benchmarks/Retry.cs index 0d5ae5ed606..ad4143c7669 100644 --- a/src/Polly.Benchmarks/Retry.cs +++ b/src/Polly.Benchmarks/Retry.cs @@ -3,78 +3,77 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class Retry { - [Config(typeof(PollyConfig))] - public class Retry + private static readonly Policy SyncPolicy = Policy.Handle().Retry(); + private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().RetryAsync(); + + [Benchmark] + public void Retry_Synchronous_Succeeds() { - private static readonly Policy SyncPolicy = Policy.Handle().Retry(); - private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().RetryAsync(); + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public void Retry_Synchronous_Succeeds() - { - SyncPolicy.Execute(() => Workloads.Action()); - } + [Benchmark] + public async Task Retry_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } - [Benchmark] - public async Task Retry_Asynchronous_Succeeds() - { - await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); - } + [Benchmark] + public async Task Retry_Asynchronous_Succeeds_With_CancellationToken() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public async Task Retry_Asynchronous_Succeeds_With_CancellationToken() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public int Retry_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public int Retry_Synchronous_With_Result_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public async Task Retry_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } - [Benchmark] - public async Task Retry_Asynchronous_With_Result_Succeeds() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } + [Benchmark] + public async Task Retry_Asynchronous_With_Result_Succeeds_With_CancellationToken() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } - [Benchmark] - public async Task Retry_Asynchronous_With_Result_Succeeds_With_CancellationToken() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } + [Benchmark] + public void Retry_Synchronous_Throws_Then_Succeeds() + { + int count = 0; - [Benchmark] - public void Retry_Synchronous_Throws_Then_Succeeds() + SyncPolicy.Execute(() => { - int count = 0; - - SyncPolicy.Execute(() => + if (count++ % 2 == 0) { - if (count++ % 2 == 0) - { - throw new InvalidOperationException(); - } - }); - } + throw new InvalidOperationException(); + } + }); + } - [Benchmark] - public async Task Retry_Asynchronous_Throws_Then_Succeeds() - { - int count = 0; + [Benchmark] + public async Task Retry_Asynchronous_Throws_Then_Succeeds() + { + int count = 0; - await AsyncPolicy.ExecuteAsync(() => + await AsyncPolicy.ExecuteAsync(() => + { + if (count++ % 2 == 0) { - if (count++ % 2 == 0) - { - throw new InvalidOperationException(); - } + throw new InvalidOperationException(); + } - return Task.CompletedTask; - }); - } + return Task.CompletedTask; + }); } } diff --git a/src/Polly.Benchmarks/Timeout.cs b/src/Polly.Benchmarks/Timeout.cs index 74de593fad6..68f84becd9c 100644 --- a/src/Polly.Benchmarks/Timeout.cs +++ b/src/Polly.Benchmarks/Timeout.cs @@ -3,54 +3,53 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +[Config(typeof(PollyConfig))] +public class Timeout { - [Config(typeof(PollyConfig))] - public class Timeout + private static readonly Policy SyncPolicy = Policy.Timeout(TimeSpan.FromMilliseconds(1)); + private static readonly AsyncPolicy AsyncPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); + + [Benchmark] + public void Timeout_Synchronous_Succeeds() + { + SyncPolicy.Execute(() => Workloads.Action()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_Succeeds_With_CancellationToken() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } + + [Benchmark] + public int Timeout_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_With_Result_Succeeds_With_CancellationToken() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } + + [Benchmark] + public async Task Timeout_Asynchronous_Times_Out_Optimistic() { - private static readonly Policy SyncPolicy = Policy.Timeout(TimeSpan.FromMilliseconds(1)); - private static readonly AsyncPolicy AsyncPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); - - [Benchmark] - public void Timeout_Synchronous_Succeeds() - { - SyncPolicy.Execute(() => Workloads.Action()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_Succeeds() - { - await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_Succeeds_With_CancellationToken() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } - - [Benchmark] - public int Timeout_Synchronous_With_Result_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_With_Result_Succeeds() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_With_Result_Succeeds_With_CancellationToken() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } - - [Benchmark] - public async Task Timeout_Asynchronous_Times_Out_Optimistic() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionInfiniteAsync(token), CancellationToken.None); - } + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionInfiniteAsync(token), CancellationToken.None); } } diff --git a/src/Polly.Benchmarks/Workloads.cs b/src/Polly.Benchmarks/Workloads.cs index 88de24136f3..9d8d8e121d4 100644 --- a/src/Polly.Benchmarks/Workloads.cs +++ b/src/Polly.Benchmarks/Workloads.cs @@ -2,50 +2,49 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Benchmarks +namespace Polly.Benchmarks; + +internal static class Workloads { - internal static class Workloads + internal static void Action() { - internal static void Action() - { - } + } - internal static Task ActionAsync() => Task.CompletedTask; + internal static Task ActionAsync() => Task.CompletedTask; - internal static Task ActionAsync(CancellationToken cancellationToken) => Task.CompletedTask; + internal static Task ActionAsync(CancellationToken cancellationToken) => Task.CompletedTask; - internal static async Task ActionInfiniteAsync() + internal static async Task ActionInfiniteAsync() + { + while (true) { - while (true) - { - await Task.Yield(); - } + await Task.Yield(); } + } - internal static async Task ActionInfiniteAsync(CancellationToken cancellationToken) + internal static async Task ActionInfiniteAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) { - while (!cancellationToken.IsCancellationRequested) - { - await Task.Yield(); - } + await Task.Yield(); } + } - internal static T Func() => default; + internal static T Func() => default; - internal static Task FuncAsync() => Task.FromResult(default); + internal static Task FuncAsync() => Task.FromResult(default); - internal static Task FuncAsync(CancellationToken cancellationToken) => Task.FromResult(default); + internal static Task FuncAsync(CancellationToken cancellationToken) => Task.FromResult(default); - internal static TResult FuncThrows() - where TException : Exception, new() - { - throw new TException(); - } + internal static TResult FuncThrows() + where TException : Exception, new() + { + throw new TException(); + } - internal static Task FuncThrowsAsync() - where TException : Exception, new() - { - throw new TException(); - } + internal static Task FuncThrowsAsync() + where TException : Exception, new() + { + throw new TException(); } } diff --git a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs index e25222f4a9e..6cbffac492a 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs @@ -8,94 +8,93 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +[Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] +public class BulkheadAsyncSpecs : BulkheadSpecsBase { - [Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] - public class BulkheadAsyncSpecs : BulkheadSpecsBase - { - public BulkheadAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + public BulkheadAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - #region Configuration + #region Configuration - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() - { - Action policy = () => Policy - .BulkheadAsync(0, 1); + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + { + Action policy = () => Policy + .BulkheadAsync(0, 1); - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_maxQueuingActions_less_than_zero() - { - Action policy = () => Policy - .BulkheadAsync(1, -1); + [Fact] + public void Should_throw_when_maxQueuingActions_less_than_zero() + { + Action policy = () => Policy + .BulkheadAsync(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .BulkheadAsync(1, 0, null); + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .BulkheadAsync(1, 0, null); - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejectedAsync"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejectedAsync"); + } - #endregion + #endregion - #region onBulkheadRejected delegate + #region onBulkheadRejected delegate - [Fact] - public async Task Should_call_onBulkheadRejected_with_passed_context() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + [Fact] + public async Task Should_call_onBulkheadRejected_with_passed_context() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - Context contextPassedToOnRejected = null; - Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; + Context contextPassedToOnRejected = null; + Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; - using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) + using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) { - TaskCompletionSource tcs = new TaskCompletionSource(); - using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) - { - _ = Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; }); }); + _ = Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; }); }); - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - await bulkhead.Awaiting(b => b.ExecuteAsync(_ => TaskHelper.EmptyTask, contextPassedToExecute)).Should().ThrowAsync(); + await bulkhead.Awaiting(b => b.ExecuteAsync(_ => TaskHelper.EmptyTask, contextPassedToExecute)).Should().ThrowAsync(); - cancellationSource.Cancel(); - tcs.SetCanceled(); - } - - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + cancellationSource.Cancel(); + tcs.SetCanceled(); } + + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); } + } - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); - } + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + } - #endregion + #endregion - } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Bulkhead/BulkheadScenario.cs b/src/Polly.Specs/Bulkhead/BulkheadScenario.cs index 1e0433afd0b..66d96640201 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadScenario.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadScenario.cs @@ -1,27 +1,26 @@ -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +internal struct BulkheadScenario { - internal struct BulkheadScenario - { - readonly int _maxParallelization; - readonly int _maxQueuingActions; - readonly int _totalTestLoad; - readonly string _scenario; - readonly bool _cancelQueuing; - readonly bool _cancelExecuting; + readonly int _maxParallelization; + readonly int _maxQueuingActions; + readonly int _totalTestLoad; + readonly string _scenario; + readonly bool _cancelQueuing; + readonly bool _cancelExecuting; - public BulkheadScenario(int maxParallelization, int maxQueuingActions, int totalTestLoad, bool cancelQueuing, bool cancelExecuting, string scenario) - { - _maxParallelization = maxParallelization; - _maxQueuingActions = maxQueuingActions; - _totalTestLoad = totalTestLoad; - _scenario = scenario; - _cancelQueuing = cancelQueuing; - _cancelExecuting = cancelExecuting; - } + public BulkheadScenario(int maxParallelization, int maxQueuingActions, int totalTestLoad, bool cancelQueuing, bool cancelExecuting, string scenario) + { + _maxParallelization = maxParallelization; + _maxQueuingActions = maxQueuingActions; + _totalTestLoad = totalTestLoad; + _scenario = scenario; + _cancelQueuing = cancelQueuing; + _cancelExecuting = cancelExecuting; + } - public object[] ToTheoryData() - { - return new object[] {_maxParallelization, _maxQueuingActions, _totalTestLoad, _cancelQueuing, _cancelExecuting, _scenario }; - } + public object[] ToTheoryData() + { + return new object[] {_maxParallelization, _maxQueuingActions, _totalTestLoad, _cancelQueuing, _cancelExecuting, _scenario }; } } diff --git a/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs b/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs index df884a239bc..69d3e930b90 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs @@ -1,25 +1,24 @@ using System.Collections; using System.Collections.Generic; -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +/// +/// A set of test scenarios used in all BulkheadPolicy tests. +/// +internal class BulkheadScenarios : IEnumerable { - /// - /// A set of test scenarios used in all BulkheadPolicy tests. - /// - internal class BulkheadScenarios : IEnumerable + public IEnumerator GetEnumerator() { - public IEnumerator GetEnumerator() - { - yield return new BulkheadScenario(maxParallelization: 5, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, not even oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 20, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: true, scenario: "A high capacity bulkhead, with no queue, not even oversubscribed; cancel some executing.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 0, totalTestLoad: 4, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 1, totalTestLoad: 5, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); - yield return new BulkheadScenario(5, 3, 8, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 1, maxQueuingActions: 6, totalTestLoad: 5, cancelQueuing: true, cancelExecuting: true, scenario: "A very tight capacity bulkhead, but which allows a huge queue; enough for all actions to be gradually processed; cancel some queuing, and some executing.").ToTheoryData(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + yield return new BulkheadScenario(maxParallelization: 5, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, not even oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 20, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: true, scenario: "A high capacity bulkhead, with no queue, not even oversubscribed; cancel some executing.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 0, totalTestLoad: 4, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 1, totalTestLoad: 5, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); + yield return new BulkheadScenario(5, 3, 8, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 1, maxQueuingActions: 6, totalTestLoad: 5, cancelQueuing: true, cancelExecuting: true, scenario: "A very tight capacity bulkhead, but which allows a huge queue; enough for all actions to be gradually processed; cancel some queuing, and some executing.").ToTheoryData(); } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs index 7e718d7ab47..dd1c684de75 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs @@ -6,93 +6,92 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +[Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] +public class BulkheadSpecs : BulkheadSpecsBase { - [Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] - public class BulkheadSpecs : BulkheadSpecsBase - { - public BulkheadSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + public BulkheadSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - #region Configuration + #region Configuration - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() - { - Action policy = () => Policy - .Bulkhead(0, 1); + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + { + Action policy = () => Policy + .Bulkhead(0, 1); - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_maxqueuedactions_less_than_zero() - { - Action policy = () => Policy - .Bulkhead(1, -1); + [Fact] + public void Should_throw_when_maxqueuedactions_less_than_zero() + { + Action policy = () => Policy + .Bulkhead(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .Bulkhead(1, 0, null); + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .Bulkhead(1, 0, null); - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejected"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejected"); + } - #endregion + #endregion - #region onBulkheadRejected delegate + #region onBulkheadRejected delegate - [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + [Fact] + public void Should_call_onBulkheadRejected_with_passed_context() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - Context contextPassedToOnRejected = null; - Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; + Context contextPassedToOnRejected = null; + Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; - using (BulkheadPolicy bulkhead = Policy.Bulkhead(1, onRejected)) - { - TaskCompletionSource tcs = new TaskCompletionSource(); + using (BulkheadPolicy bulkhead = Policy.Bulkhead(1, onRejected)) + { + TaskCompletionSource tcs = new TaskCompletionSource(); - Task.Run(() => { bulkhead.Execute(() => { tcs.Task.Wait(); }); }); + Task.Run(() => { bulkhead.Execute(() => { tcs.Task.Wait(); }); }); - // Time for the other thread to kick up and take the bulkhead. - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); + // Time for the other thread to kick up and take the bulkhead. + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - bulkhead.Invoking(b => b.Execute(_ => { }, contextPassedToExecute)).Should() - .Throw(); + bulkhead.Invoking(b => b.Execute(_ => { }, contextPassedToExecute)).Should() + .Throw(); - tcs.SetCanceled(); + tcs.SetCanceled(); - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); - } + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); } + } - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.Bulkhead(maxParallelization, maxQueuingActions); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.Bulkhead(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); - } + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs b/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs index 4d6a6fa9420..c014bd99c43 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs @@ -9,386 +9,385 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +[Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] +public abstract class BulkheadSpecsBase : IDisposable { - [Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] - public abstract class BulkheadSpecsBase : IDisposable - { - #region Time constraints + #region Time constraints - protected readonly TimeSpan ShimTimeSpan = TimeSpan.FromMilliseconds(50); // How frequently to retry the assertions. - protected readonly TimeSpan CohesionTimeLimit = TimeSpan.FromMilliseconds(1000); // Consider increasing CohesionTimeLimit if bulkhead specs fail transiently in slower build environments. + protected readonly TimeSpan ShimTimeSpan = TimeSpan.FromMilliseconds(50); // How frequently to retry the assertions. + protected readonly TimeSpan CohesionTimeLimit = TimeSpan.FromMilliseconds(1000); // Consider increasing CohesionTimeLimit if bulkhead specs fail transiently in slower build environments. - #endregion - public BulkheadSpecsBase(ITestOutputHelper testOutputHelper) - { + #endregion + public BulkheadSpecsBase(ITestOutputHelper testOutputHelper) + { #if !DEBUG - TestOutputHelper = new SilentOutputHelper(); + TestOutputHelper = new SilentOutputHelper(); #else - TestOutputHelper = new AnnotatedOutputHelper(testOutputHelper); + TestOutputHelper = new AnnotatedOutputHelper(testOutputHelper); #endif #if !NETCOREAPP1_1 - ThreadPool.SetMinThreads(50, 20); + ThreadPool.SetMinThreads(50, 20); #endif - } + } - #region Operating variables + #region Operating variables - protected IBulkheadPolicy BulkheadForStats { get; set; } + protected IBulkheadPolicy BulkheadForStats { get; set; } - internal TraceableAction[] Actions { get; set; } + internal TraceableAction[] Actions { get; set; } - protected Task[] Tasks { get; set; } + protected Task[] Tasks { get; set; } - protected readonly AutoResetEvent StatusChangedEvent = new AutoResetEvent(false); + protected readonly AutoResetEvent StatusChangedEvent = new AutoResetEvent(false); - #endregion + #endregion - #region Scenario + #region Scenario - protected string Scenario { get; set; } + protected string Scenario { get; set; } - protected int MaxParallelization { get; set; } - protected int MaxQueuingActions { get; set; } - protected int TotalActions { get; set; } + protected int MaxParallelization { get; set; } + protected int MaxQueuingActions { get; set; } + protected int TotalActions { get; set; } - #endregion + #endregion - #region Tracked metrics + #region Tracked metrics - protected int ExpectedCompleted { get; set; } - protected int ExpectedCancelled { get; set; } - protected int ExpectedExecuting { get; set; } - protected int ExpectedRejects { get; set; } - protected int ExpectedQueuing { get; set; } - protected int ExpectedFaulted { get; set; } = 0; - protected int ExpectedBulkheadFree { get; set; } - protected int ExpectedQueueFree { get; set; } + protected int ExpectedCompleted { get; set; } + protected int ExpectedCancelled { get; set; } + protected int ExpectedExecuting { get; set; } + protected int ExpectedRejects { get; set; } + protected int ExpectedQueuing { get; set; } + protected int ExpectedFaulted { get; set; } = 0; + protected int ExpectedBulkheadFree { get; set; } + protected int ExpectedQueueFree { get; set; } - protected int ActualCompleted { get; set; } - protected int ActualCancelled { get; set; } - protected int ActualExecuting { get; set; } - protected int ActualRejects { get; set; } - protected int ActualQueuing { get; set; } - protected int ActualFaulted { get; set; } - protected int ActualBulkheadFree => BulkheadForStats.BulkheadAvailableCount; - protected int ActualQueueFree => BulkheadForStats.QueueAvailableCount; + protected int ActualCompleted { get; set; } + protected int ActualCancelled { get; set; } + protected int ActualExecuting { get; set; } + protected int ActualRejects { get; set; } + protected int ActualQueuing { get; set; } + protected int ActualFaulted { get; set; } + protected int ActualBulkheadFree => BulkheadForStats.BulkheadAvailableCount; + protected int ActualQueueFree => BulkheadForStats.QueueAvailableCount; - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected abstract IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions); + protected abstract IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions); - protected abstract Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action); + protected abstract Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action); - [Theory, ClassData(typeof(BulkheadScenarios))] - public void Should_control_executions_per_specification(int maxParallelization, int maxQueuingActions, int totalActions, bool cancelQueuing, bool cancelExecuting, string scenario) - { - if (totalActions < 0) throw new ArgumentOutOfRangeException(nameof(totalActions)); + [Theory, ClassData(typeof(BulkheadScenarios))] + public void Should_control_executions_per_specification(int maxParallelization, int maxQueuingActions, int totalActions, bool cancelQueuing, bool cancelExecuting, string scenario) + { + if (totalActions < 0) throw new ArgumentOutOfRangeException(nameof(totalActions)); - MaxParallelization = maxParallelization; - MaxQueuingActions = maxQueuingActions; - TotalActions = totalActions; - Scenario = $"MaxParallelization {maxParallelization}; MaxQueuing {maxQueuingActions}; TotalActions {totalActions}; CancelQueuing {cancelQueuing}; CancelExecuting {cancelExecuting}: {scenario}"; + MaxParallelization = maxParallelization; + MaxQueuingActions = maxQueuingActions; + TotalActions = totalActions; + Scenario = $"MaxParallelization {maxParallelization}; MaxQueuing {maxQueuingActions}; TotalActions {totalActions}; CancelQueuing {cancelQueuing}; CancelExecuting {cancelExecuting}: {scenario}"; - IBulkheadPolicy bulkhead = GetBulkhead(maxParallelization, maxQueuingActions); - using (bulkhead) - { - BulkheadForStats = bulkhead; + IBulkheadPolicy bulkhead = GetBulkhead(maxParallelization, maxQueuingActions); + using (bulkhead) + { + BulkheadForStats = bulkhead; - // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). - Actions = new TraceableAction[totalActions]; - for (int i = 0; i < totalActions; i++) { Actions[i] = new TraceableAction(i, StatusChangedEvent, TestOutputHelper); } + // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). + Actions = new TraceableAction[totalActions]; + for (int i = 0; i < totalActions; i++) { Actions[i] = new TraceableAction(i, StatusChangedEvent, TestOutputHelper); } - // Throw all the delegates at the bulkhead simultaneously. - Tasks = new Task[totalActions]; - for (int i = 0; i < totalActions; i++) { Tasks[i] = ExecuteOnBulkhead(bulkhead, Actions[i]); } + // Throw all the delegates at the bulkhead simultaneously. + Tasks = new Task[totalActions]; + for (int i = 0; i < totalActions; i++) { Tasks[i] = ExecuteOnBulkhead(bulkhead, Actions[i]); } - OutputStatus("Immediately after queueing..."); + OutputStatus("Immediately after queueing..."); - // Assert the expected distributions of executing, queuing, rejected and completed - when all delegates thrown at bulkhead. - ExpectedCompleted = 0; - ExpectedCancelled = 0; - ExpectedExecuting = Math.Min(totalActions, maxParallelization); - ExpectedRejects = Math.Max(0, totalActions - maxParallelization - maxQueuingActions); - ExpectedQueuing = Math.Min(maxQueuingActions, Math.Max(0, totalActions - maxParallelization)); - ExpectedBulkheadFree = maxParallelization - ExpectedExecuting; - ExpectedQueueFree = maxQueuingActions - ExpectedQueuing; + // Assert the expected distributions of executing, queuing, rejected and completed - when all delegates thrown at bulkhead. + ExpectedCompleted = 0; + ExpectedCancelled = 0; + ExpectedExecuting = Math.Min(totalActions, maxParallelization); + ExpectedRejects = Math.Max(0, totalActions - maxParallelization - maxQueuingActions); + ExpectedQueuing = Math.Min(maxQueuingActions, Math.Max(0, totalActions - maxParallelization)); + ExpectedBulkheadFree = maxParallelization - ExpectedExecuting; + ExpectedQueueFree = maxQueuingActions - ExpectedQueuing; - try - { - Within(CohesionTimeLimit, ActualsMatchExpecteds); - } - finally + try + { + Within(CohesionTimeLimit, ActualsMatchExpecteds); + } + finally + { + OutputStatus("Expected initial state verified..."); + } + + // Complete or cancel delegates one by one, and expect others to take their place (if a slot released and others remain queueing); until all work is done. + while (ExpectedExecuting > 0) + { + if (cancelQueuing) { - OutputStatus("Expected initial state verified..."); - } + TestOutputHelper.WriteLine("Cancelling a queueing task..."); + + Actions.First(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Cancel(); - // Complete or cancel delegates one by one, and expect others to take their place (if a slot released and others remain queueing); until all work is done. - while (ExpectedExecuting > 0) + ExpectedCancelled++; + ExpectedQueuing--; + ExpectedQueueFree++; + + cancelQueuing = false; + } + else if (cancelExecuting) { - if (cancelQueuing) - { - TestOutputHelper.WriteLine("Cancelling a queueing task..."); + TestOutputHelper.WriteLine("Cancelling an executing task..."); - Actions.First(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Cancel(); + Actions.First(a => a.Status == TraceableActionStatus.Executing).Cancel(); - ExpectedCancelled++; + ExpectedCancelled++; + if (ExpectedQueuing > 0) + { ExpectedQueuing--; ExpectedQueueFree++; - - cancelQueuing = false; } - else if (cancelExecuting) + else { - TestOutputHelper.WriteLine("Cancelling an executing task..."); - - Actions.First(a => a.Status == TraceableActionStatus.Executing).Cancel(); - - ExpectedCancelled++; - if (ExpectedQueuing > 0) - { - ExpectedQueuing--; - ExpectedQueueFree++; - } - else - { - ExpectedExecuting--; - ExpectedBulkheadFree++; - } - - cancelExecuting = false; + ExpectedExecuting--; + ExpectedBulkheadFree++; } - else // Complete an executing delegate. - { - TestOutputHelper.WriteLine("Completing a task..."); - Actions.First(a => a.Status == TraceableActionStatus.Executing).AllowCompletion(); + cancelExecuting = false; + } + else // Complete an executing delegate. + { + TestOutputHelper.WriteLine("Completing a task..."); - ExpectedCompleted++; + Actions.First(a => a.Status == TraceableActionStatus.Executing).AllowCompletion(); - if (ExpectedQueuing > 0) - { - ExpectedQueuing--; - ExpectedQueueFree++; - } - else - { - ExpectedExecuting--; - ExpectedBulkheadFree++; - } + ExpectedCompleted++; - } - - try + if (ExpectedQueuing > 0) { - Within(CohesionTimeLimit, ActualsMatchExpecteds); + ExpectedQueuing--; + ExpectedQueueFree++; } - finally + else { - OutputStatus("End of next loop iteration..."); + ExpectedExecuting--; + ExpectedBulkheadFree++; } } - EnsureNoUnbservedTaskExceptions(); - - TestOutputHelper.WriteLine("Verifying all tasks completed..."); - Within(CohesionTimeLimit, AllTasksCompleted); - } - } - - protected void UpdateActuals() - { - ActualCompleted = ActualCancelled = ActualExecuting = ActualRejects = ActualQueuing = ActualFaulted = 0; - - foreach (TraceableAction action in Actions) - { - switch (action.Status) + try + { + Within(CohesionTimeLimit, ActualsMatchExpecteds); + } + finally { - case TraceableActionStatus.Canceled: - ActualCancelled++; - break; - case TraceableActionStatus.Completed: - ActualCompleted++; - break; - case TraceableActionStatus.Executing: - ActualExecuting++; - break; - case TraceableActionStatus.Faulted: - ActualFaulted++; - break; - case TraceableActionStatus.QueueingForSemaphore: - ActualQueuing++; - break; - case TraceableActionStatus.Rejected: - ActualRejects++; - break; - case TraceableActionStatus.StartRequested: - case TraceableActionStatus.Unstarted: - // We do not care to count these. - break; - default: - throw new InvalidOperationException($"Unaccounted for {nameof(TraceableActionStatus)}: {action.Status}."); + OutputStatus("End of next loop iteration..."); } - } - } - protected AssertionFailure ActualsMatchExpecteds() - { - UpdateActuals(); - - if (ExpectedFaulted != ActualFaulted) - { - return new AssertionFailure(ExpectedFaulted, ActualFaulted, nameof(ExpectedFaulted)); } - if (ExpectedRejects != ActualRejects) - { - return new AssertionFailure(ExpectedRejects, ActualRejects, nameof(ExpectedRejects)); - } + EnsureNoUnbservedTaskExceptions(); - if (ExpectedCancelled != ActualCancelled) - { - return new AssertionFailure(ExpectedCancelled, ActualCancelled, nameof(ExpectedCancelled)); - } + TestOutputHelper.WriteLine("Verifying all tasks completed..."); + Within(CohesionTimeLimit, AllTasksCompleted); + } + } - if (ExpectedCompleted != ActualCompleted) - { - return new AssertionFailure(ExpectedCompleted, ActualCompleted, nameof(ExpectedCompleted)); - } + protected void UpdateActuals() + { + ActualCompleted = ActualCancelled = ActualExecuting = ActualRejects = ActualQueuing = ActualFaulted = 0; - if (ExpectedExecuting != ActualExecuting) + foreach (TraceableAction action in Actions) + { + switch (action.Status) { - return new AssertionFailure(ExpectedExecuting, ActualExecuting, nameof(ExpectedExecuting)); + case TraceableActionStatus.Canceled: + ActualCancelled++; + break; + case TraceableActionStatus.Completed: + ActualCompleted++; + break; + case TraceableActionStatus.Executing: + ActualExecuting++; + break; + case TraceableActionStatus.Faulted: + ActualFaulted++; + break; + case TraceableActionStatus.QueueingForSemaphore: + ActualQueuing++; + break; + case TraceableActionStatus.Rejected: + ActualRejects++; + break; + case TraceableActionStatus.StartRequested: + case TraceableActionStatus.Unstarted: + // We do not care to count these. + break; + default: + throw new InvalidOperationException($"Unaccounted for {nameof(TraceableActionStatus)}: {action.Status}."); } + } + } - if (ExpectedQueuing != ActualQueuing) - { - return new AssertionFailure(ExpectedQueuing, ActualQueuing, nameof(ExpectedQueuing)); - } + protected AssertionFailure ActualsMatchExpecteds() + { + UpdateActuals(); - if (ExpectedBulkheadFree != ActualBulkheadFree) - { - return new AssertionFailure(ExpectedBulkheadFree, ActualBulkheadFree, nameof(ExpectedBulkheadFree)); - } + if (ExpectedFaulted != ActualFaulted) + { + return new AssertionFailure(ExpectedFaulted, ActualFaulted, nameof(ExpectedFaulted)); + } - if (ExpectedQueueFree != ActualQueueFree) - { - return new AssertionFailure(ExpectedQueueFree, ActualQueueFree, nameof(ExpectedQueueFree)); - } + if (ExpectedRejects != ActualRejects) + { + return new AssertionFailure(ExpectedRejects, ActualRejects, nameof(ExpectedRejects)); + } - return null; + if (ExpectedCancelled != ActualCancelled) + { + return new AssertionFailure(ExpectedCancelled, ActualCancelled, nameof(ExpectedCancelled)); } - protected AssertionFailure AllTasksCompleted() + if (ExpectedCompleted != ActualCompleted) { - int countTasksCompleted = Tasks.Count(t => t.IsCompleted); - if (countTasksCompleted < TotalActions) - { - return new AssertionFailure(TotalActions, countTasksCompleted, nameof(countTasksCompleted)); - } + return new AssertionFailure(ExpectedCompleted, ActualCompleted, nameof(ExpectedCompleted)); + } - return null; + if (ExpectedExecuting != ActualExecuting) + { + return new AssertionFailure(ExpectedExecuting, ActualExecuting, nameof(ExpectedExecuting)); } - protected void EnsureNoUnbservedTaskExceptions() + if (ExpectedQueuing != ActualQueuing) { - for (int i = 0; i < Tasks.Length; i++) - { - try - { - Tasks[i].Wait(); - } - catch (Exception e) - { - throw new Exception("Task " + i + " raised the following unobserved task exception: ", e); - } - } + return new AssertionFailure(ExpectedQueuing, ActualQueuing, nameof(ExpectedQueuing)); } - #endregion + if (ExpectedBulkheadFree != ActualBulkheadFree) + { + return new AssertionFailure(ExpectedBulkheadFree, ActualBulkheadFree, nameof(ExpectedBulkheadFree)); + } - protected AssertionFailure Expect(int expected, Func actualFunc, string measure) + if (ExpectedQueueFree != ActualQueueFree) { - int actual = actualFunc(); - return actual != expected ? new AssertionFailure(expected, actual, measure) : null; + return new AssertionFailure(ExpectedQueueFree, ActualQueueFree, nameof(ExpectedQueueFree)); } - protected void Within(TimeSpan timeSpan, Func actionContainingAssertions) + return null; + } + + protected AssertionFailure AllTasksCompleted() + { + int countTasksCompleted = Tasks.Count(t => t.IsCompleted); + if (countTasksCompleted < TotalActions) { - TimeSpan permitted = timeSpan; - Stopwatch watch = Stopwatch.StartNew(); - while (true) - { - var potentialFailure = actionContainingAssertions(); - if (potentialFailure == null) - { - break; - } + return new AssertionFailure(TotalActions, countTasksCompleted, nameof(countTasksCompleted)); + } - if (watch.Elapsed > permitted) - { - TestOutputHelper.WriteLine("Failing assertion on: {0}", potentialFailure.Measure); - potentialFailure.Actual.Should().Be(potentialFailure.Expected, $"for '{potentialFailure.Measure}', in scenario: {Scenario}"); - throw new InvalidOperationException("Code should never reach here. Preceding assertion should fail."); - } + return null; + } - bool signaled = StatusChangedEvent.WaitOne(ShimTimeSpan); - if (signaled) - { - // Following TraceableAction.CaptureCompletion() signalling the AutoResetEvent, - // there can be race conditions between on the one hand exiting the bulkhead semaphore (and potentially another execution gaining it), - // and the assertion being verified here about those same facts. - // If that race is lost by the real-world state change, and the AutoResetEvent signal occurred very close to timeoutTime, - // there might not be a second chance. - // We therefore permit another shim time for the condition to come good. - permitted += CohesionTimeLimit; - } + protected void EnsureNoUnbservedTaskExceptions() + { + for (int i = 0; i < Tasks.Length; i++) + { + try + { + Tasks[i].Wait(); + } + catch (Exception e) + { + throw new Exception("Task " + i + " raised the following unobserved task exception: ", e); } } + } - #region Output helpers + #endregion - protected readonly ITestOutputHelper TestOutputHelper; + protected AssertionFailure Expect(int expected, Func actualFunc, string measure) + { + int actual = actualFunc(); + return actual != expected ? new AssertionFailure(expected, actual, measure) : null; + } - protected void OutputStatus(string statusHeading) + protected void Within(TimeSpan timeSpan, Func actionContainingAssertions) + { + TimeSpan permitted = timeSpan; + Stopwatch watch = Stopwatch.StartNew(); + while (true) { - TestOutputHelper.WriteLine(statusHeading); - TestOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", ActualBulkheadFree, MaxParallelization); - TestOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", ActualQueueFree, MaxQueuingActions); + var potentialFailure = actionContainingAssertions(); + if (potentialFailure == null) + { + break; + } + + if (watch.Elapsed > permitted) + { + TestOutputHelper.WriteLine("Failing assertion on: {0}", potentialFailure.Measure); + potentialFailure.Actual.Should().Be(potentialFailure.Expected, $"for '{potentialFailure.Measure}', in scenario: {Scenario}"); + throw new InvalidOperationException("Code should never reach here. Preceding assertion should fail."); + } - for (int i = 0; i < Actions.Length; i++) + bool signaled = StatusChangedEvent.WaitOne(ShimTimeSpan); + if (signaled) { - TestOutputHelper.WriteLine("Action {0}: {1}", i, Actions[i].Status); + // Following TraceableAction.CaptureCompletion() signalling the AutoResetEvent, + // there can be race conditions between on the one hand exiting the bulkhead semaphore (and potentially another execution gaining it), + // and the assertion being verified here about those same facts. + // If that race is lost by the real-world state change, and the AutoResetEvent signal occurred very close to timeoutTime, + // there might not be a second chance. + // We therefore permit another shim time for the condition to come good. + permitted += CohesionTimeLimit; } - TestOutputHelper.WriteLine(String.Empty); } + } + + #region Output helpers + + protected readonly ITestOutputHelper TestOutputHelper; + + protected void OutputStatus(string statusHeading) + { + TestOutputHelper.WriteLine(statusHeading); + TestOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", ActualBulkheadFree, MaxParallelization); + TestOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", ActualQueueFree, MaxQueuingActions); - private void ShowTestOutput() + for (int i = 0; i < Actions.Length; i++) { - ((AnnotatedOutputHelper) TestOutputHelper).Flush(); + TestOutputHelper.WriteLine("Action {0}: {1}", i, Actions[i].Status); } + TestOutputHelper.WriteLine(String.Empty); + } - #endregion + private void ShowTestOutput() + { + ((AnnotatedOutputHelper) TestOutputHelper).Flush(); + } - public void Dispose() - { + #endregion + + public void Dispose() + { #if DEBUG - ShowTestOutput(); + ShowTestOutput(); #endif - if (Actions != null) + if (Actions != null) + { + foreach (TraceableAction action in Actions) { - foreach (TraceableAction action in Actions) - { - action.Dispose(); - } + action.Dispose(); } - - StatusChangedEvent.Dispose(); } + + StatusChangedEvent.Dispose(); } } diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs index f5ca561eaab..97c5abaf65c 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs @@ -9,102 +9,101 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +[Collection(Constants.ParallelThreadDependentTestCollection)] +public class BulkheadTResultAsyncSpecs : BulkheadSpecsBase { - [Collection(Constants.ParallelThreadDependentTestCollection)] - public class BulkheadTResultAsyncSpecs : BulkheadSpecsBase + public BulkheadTResultAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + + #region Configuration + + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() { - public BulkheadTResultAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + Action policy = () => Policy + .BulkheadAsync(0, 1); - #region Configuration + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() - { - Action policy = () => Policy - .BulkheadAsync(0, 1); + [Fact] + public void Should_throw_when_maxQueuingActions_less_than_zero() + { + Action policy = () => Policy + .BulkheadAsync(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - [Fact] - public void Should_throw_when_maxQueuingActions_less_than_zero() - { - Action policy = () => Policy - .BulkheadAsync(1, -1); + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .BulkheadAsync(1, 0, null); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejectedAsync"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .BulkheadAsync(1, 0, null); + #endregion - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejectedAsync"); - } + #region onBulkheadRejected delegate - #endregion - - #region onBulkheadRejected delegate - - [Fact] - public async Task Should_call_onBulkheadRejected_with_passed_context() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnRejected = null; - Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; - - using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) - { - TaskCompletionSource tcs = new TaskCompletionSource(); - using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) - { - _ = Task.Run(() => { - bulkhead.ExecuteAsync(async () => - { - await tcs.Task; - return 0; - }); + [Fact] + public async Task Should_call_onBulkheadRejected_with_passed_context() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnRejected = null; + Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; + + using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) + { + _ = Task.Run(() => { + bulkhead.ExecuteAsync(async () => + { + await tcs.Task; + return 0; }); + }); - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - await bulkhead.Awaiting(b => b.ExecuteAsync(_ => Task.FromResult(1), contextPassedToExecute)).Should().ThrowAsync(); + await bulkhead.Awaiting(b => b.ExecuteAsync(_ => Task.FromResult(1), contextPassedToExecute)).Should().ThrowAsync(); - cancellationSource.Cancel(); - tcs.SetCanceled(); - } + cancellationSource.Cancel(); + tcs.SetCanceled(); + } - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); - } } + } - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); - } + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs index 3dded928396..3100343ba2e 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs @@ -9,99 +9,98 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +[Collection(Constants.ParallelThreadDependentTestCollection)] +public class BulkheadTResultSpecs : BulkheadSpecsBase { - [Collection(Constants.ParallelThreadDependentTestCollection)] - public class BulkheadTResultSpecs : BulkheadSpecsBase + public BulkheadTResultSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + + #region Configuration + + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() { - public BulkheadTResultSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + Action policy = () => Policy + .Bulkhead(0, 1); - #region Configuration + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() - { - Action policy = () => Policy - .Bulkhead(0, 1); + [Fact] + public void Should_throw_when_maxQueuingActions_less_than_zero() + { + Action policy = () => Policy + .Bulkhead(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - [Fact] - public void Should_throw_when_maxQueuingActions_less_than_zero() - { - Action policy = () => Policy - .Bulkhead(1, -1); + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .Bulkhead(1, 0, null); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejected"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .Bulkhead(1, 0, null); + #endregion - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejected"); - } + #region onBulkheadRejected delegate - #endregion - - #region onBulkheadRejected delegate - - [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnRejected = null; - Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; - - using (BulkheadPolicy bulkhead = Policy.Bulkhead(1, onRejected)) - { - TaskCompletionSource tcs = new TaskCompletionSource(); - using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) - { - Task.Run(() => { - bulkhead.Execute(() => - { - tcs.Task.Wait(); - return 0; - }); + [Fact] + public void Should_call_onBulkheadRejected_with_passed_context() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnRejected = null; + Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; + + using (BulkheadPolicy bulkhead = Policy.Bulkhead(1, onRejected)) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) + { + Task.Run(() => { + bulkhead.Execute(() => + { + tcs.Task.Wait(); + return 0; }); + }); - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - - bulkhead.Invoking(b => b.Execute(_ => 1, contextPassedToExecute)).Should().Throw(); + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - cancellationSource.Cancel(); - tcs.SetCanceled(); - } + bulkhead.Invoking(b => b.Execute(_ => 1, contextPassedToExecute)).Should().Throw(); - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + cancellationSource.Cancel(); + tcs.SetCanceled(); } - } - #endregion + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + } + } - #region Bulkhead behaviour + #endregion - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.Bulkhead(maxParallelization, maxQueuingActions); - } + #region Bulkhead behaviour - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.Bulkhead(maxParallelization, maxQueuingActions); + } - #endregion + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); } + + #endregion } diff --git a/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs b/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs index 471df56b9f1..4b6d5b1904a 100644 --- a/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs +++ b/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs @@ -2,24 +2,23 @@ using Polly.Bulkhead; using Xunit; -namespace Polly.Specs.Bulkhead +namespace Polly.Specs.Bulkhead; + +public class IBulkheadPolicySpecs { - public class IBulkheadPolicySpecs + [Fact] + public void Should_be_able_to_use_BulkheadAvailableCount_via_interface() { - [Fact] - public void Should_be_able_to_use_BulkheadAvailableCount_via_interface() - { - IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); + IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); - bulkhead.BulkheadAvailableCount.Should().Be(20); - } + bulkhead.BulkheadAvailableCount.Should().Be(20); + } - [Fact] - public void Should_be_able_to_use_QueueAvailableCount_via_interface() - { - IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); + [Fact] + public void Should_be_able_to_use_QueueAvailableCount_via_interface() + { + IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); - bulkhead.QueueAvailableCount.Should().Be(10); - } + bulkhead.QueueAvailableCount.Should().Be(10); } } diff --git a/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs b/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs index 640a413d63e..ebd4c427fda 100644 --- a/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs +++ b/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs @@ -5,58 +5,57 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class AbsoluteTtlSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class AbsoluteTtlSpecs : IDisposable + [Fact] + public void Should_be_able_to_configure_for_near_future_time() + { + Action configure = () => new AbsoluteTtl(DateTime.Today.AddDays(1)); + + configure.Should().NotThrow(); + } + + [Fact] + public void Should_be_able_to_configure_for_far_future() + { + Action configure = () => new AbsoluteTtl(DateTimeOffset.MaxValue); + + configure.Should().NotThrow(); + } + + [Fact] + public void Should_be_able_to_configure_for_past() + { + Action configure = () => new AbsoluteTtl(DateTimeOffset.MinValue); + + configure.Should().NotThrow(); + } + + [Fact] + public void Should_return_zero_ttl_if_configured_to_expire_in_past() + { + AbsoluteTtl ttlStrategy = new AbsoluteTtl(SystemClock.DateTimeOffsetUtcNow().Subtract(TimeSpan.FromTicks(1))); + + ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); + } + + [Fact] + public void Should_return_timespan_reflecting_time_until_expiry() + { + DateTime today = DateTime.Today; + DateTime tomorrow = today.AddDays(1); + + AbsoluteTtl ttlStrategy = new AbsoluteTtl(tomorrow); + + SystemClock.DateTimeOffsetUtcNow = () => today; + ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.FromDays(1)); + } + + public void Dispose() { - [Fact] - public void Should_be_able_to_configure_for_near_future_time() - { - Action configure = () => new AbsoluteTtl(DateTime.Today.AddDays(1)); - - configure.Should().NotThrow(); - } - - [Fact] - public void Should_be_able_to_configure_for_far_future() - { - Action configure = () => new AbsoluteTtl(DateTimeOffset.MaxValue); - - configure.Should().NotThrow(); - } - - [Fact] - public void Should_be_able_to_configure_for_past() - { - Action configure = () => new AbsoluteTtl(DateTimeOffset.MinValue); - - configure.Should().NotThrow(); - } - - [Fact] - public void Should_return_zero_ttl_if_configured_to_expire_in_past() - { - AbsoluteTtl ttlStrategy = new AbsoluteTtl(SystemClock.DateTimeOffsetUtcNow().Subtract(TimeSpan.FromTicks(1))); - - ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); - } - - [Fact] - public void Should_return_timespan_reflecting_time_until_expiry() - { - DateTime today = DateTime.Today; - DateTime tomorrow = today.AddDays(1); - - AbsoluteTtl ttlStrategy = new AbsoluteTtl(tomorrow); - - SystemClock.DateTimeOffsetUtcNow = () => today; - ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.FromDays(1)); - } - - public void Dispose() - { - SystemClock.Reset(); - } + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs index 83fdb8091a1..0d6f59c5c9a 100644 --- a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs @@ -8,703 +8,702 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching -{ - [Collection(Constants.SystemClockDependentTestCollection)] - public class CacheAsyncSpecs : IDisposable - { - #region Configuration +namespace Polly.Specs.Caching; - [Fact] - public void Should_throw_when_cache_provider_is_null() - { - IAsyncCacheProvider cacheProvider = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } +[Collection(Constants.SystemClockDependentTestCollection)] +public class CacheAsyncSpecs : IDisposable +{ + #region Configuration - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } - - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } + [Fact] + public void Should_throw_when_cache_provider_is_null() + { + IAsyncCacheProvider cacheProvider = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - #endregion + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } - #region Caching behaviours + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + #endregion - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + #region Caching behaviours - bool delegateExecuted = false; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - delegateExecuted.Should().BeFalse(); - } + bool delegateExecuted = false; - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + (await cache.ExecuteAsync(async _ => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + delegateExecuted.Should().BeFalse(); + } - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - TimeSpan ttl = TimeSpan.FromMinutes(30); - var cache = Policy.CacheAsync(stubCacheProvider, ttl); - - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); - - int delegateInvocations = 0; - Func> func = async _ => - { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; - - DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - - // First execution should execute delegate and put result in the cache. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } - - [Fact] - public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + TimeSpan ttl = TimeSpan.FromMinutes(30); + var cache = Policy.CacheAsync(stubCacheProvider, ttl); - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + int delegateInvocations = 0; + Func> func = async _ => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; + + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; + + // First execution should execute delegate and put result in the cache. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); + + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + [Fact] + public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - int delegateInvocations = 0; - Func> func = async _ => - { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - [Fact] - public async Task Should_allow_custom_FuncCacheKeyStrategy() - { - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - object person1 = new object(); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - object person2 = new object(); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - bool funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; + int delegateInvocations = 0; + Func> func = async _ => + { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - [Fact] - public async Task Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + [Fact] + public async Task Should_allow_custom_FuncCacheKeyStrategy() + { + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - object person1 = new object(); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - object person2 = new object(); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + object person1 = new object(); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + object person2 = new object(); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - bool funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; + bool funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - #endregion + [Fact] + public async Task Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - #region Caching behaviours, default(TResult) + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + object person1 = new object(); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + object person2 = new object(); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + bool funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + #endregion - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + #region Caching behaviours, default(TResult) - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - bool delegateExecuted = false; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - delegateExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + bool delegateExecuted = false; - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (await cache.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() - { - ResultPrimitive valueToReturnFromCache = default; - ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + delegateExecuted.Should().BeFalse(); + } - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - bool delegateExecuted = false; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - delegateExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - #endregion + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - #region Non-generic CachePolicy in non-generic PolicyWrap + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + + bool delegateExecuted = false; + + (await cache.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); + + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + #endregion - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(cache, noop); + #region Non-generic CachePolicy in non-generic PolicyWrap - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - bool delegateExecuted = false; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(cache, noop); - (await wrap.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - delegateExecuted.Should().BeFalse(); - } + bool delegateExecuted = false; - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + (await wrap.ExecuteAsync(async _ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(noop, cache); + delegateExecuted.Should().BeFalse(); + } - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - bool delegateExecuted = false; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(noop, cache); - (await wrap.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - delegateExecuted.Should().BeFalse(); - } + bool delegateExecuted = false; - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + (await wrap.ExecuteAsync(async _ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; - - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(noop, cache, noop); + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - - bool delegateExecuted = false; + delegateExecuted.Should().BeFalse(); + } - (await wrap.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(noop, cache, noop); - #endregion + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - #region No-op pass-through behaviour + bool delegateExecuted = false; - [Fact] - public async Task Should_always_execute_delegate_if_execution_key_not_set() + (await wrap.ExecuteAsync(async _ => { - string valueToReturn = Guid.NewGuid().ToString(); + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + delegateExecuted.Should().BeFalse(); + } - int delegateInvocations = 0; - Func> func = async () => { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + #endregion - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + #region No-op pass-through behaviour - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + [Fact] + public async Task Should_always_execute_delegate_if_execution_key_not_set() + { + string valueToReturn = Guid.NewGuid().ToString(); - [Fact] - public void Should_always_execute_delegate_if_execution_is_void_returning() - { - string operationKey = "SomeKey"; + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + int delegateInvocations = 0; + Func> func = async () => { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - int delegateInvocations = 0; - Func action = async _ => { delegateInvocations++; await TaskHelper.EmptyTask; }; + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.ExecuteAsync(action, new Context(operationKey)); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - cache.ExecuteAsync(action, new Context(operationKey)); - delegateInvocations.Should().Be(2); - } + [Fact] + public void Should_always_execute_delegate_if_execution_is_void_returning() + { + string operationKey = "SomeKey"; - #endregion + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - #region Cancellation + int delegateInvocations = 0; + Func action = async _ => { delegateInvocations++; await TaskHelper.EmptyTask; }; - [Fact] - public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + cache.ExecuteAsync(action, new Context(operationKey)); + delegateInvocations.Should().Be(1); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + cache.ExecuteAsync(action, new Context(operationKey)); + delegateInvocations.Should().Be(2); + } - CancellationTokenSource tokenSource = new CancellationTokenSource(); + #endregion - int delegateInvocations = 0; - Func> func = async (_, _) => - { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + #region Cancellation - (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + [Fact] + public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - tokenSource.Cancel(); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().ThrowAsync(); - delegateInvocations.Should().Be(1); - } + CancellationTokenSource tokenSource = new CancellationTokenSource(); - [Fact] - public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + int delegateInvocations = 0; + Func> func = async (_, _) => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - CancellationTokenSource tokenSource = new CancellationTokenSource(); + (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - Func> func = async (_, ct) => - { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - await TaskHelper.EmptyTask; - return valueToReturn; - }; + tokenSource.Cancel(); - await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().ThrowAsync(); + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); + delegateInvocations.Should().Be(1); + } - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + [Fact] + public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - #endregion + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - #region Policy hooks + CancellationTokenSource tokenSource = new CancellationTokenSource(); - [Fact] - public async Task Should_call_onError_delegate_if_cache_get_errors() + Func> func = async (_, ct) => { - Exception ex = new Exception(); - IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + await TaskHelper.EmptyTask; + return valueToReturn; + }; + + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); + + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } - Exception exceptionFromCacheProvider = null; + #endregion - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + #region Policy hooks - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + [Fact] + public async Task Should_call_onError_delegate_if_cache_get_errors() + { + Exception ex = new Exception(); + IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); + Exception exceptionFromCacheProvider = null; - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - bool delegateExecuted = false; + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - // Even though value is in cache, get will error; so value is returned from execution. - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - }, new Context(operationKey))) - .Should().Be(valueToReturnFromExecution); - delegateExecuted.Should().BeTrue(); + bool delegateExecuted = false; - // And error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); - } - [Fact] - public async Task Should_call_onError_delegate_if_cache_put_errors() + // Even though value is in cache, get will error; so value is returned from execution. + (await cache.ExecuteAsync(async _ => { - Exception ex = new Exception(); - IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; - Exception exceptionFromCacheProvider = null; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromExecution); + delegateExecuted.Should().BeTrue(); - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + // And error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); + } - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + [Fact] + public async Task Should_call_onError_delegate_if_cache_put_errors() + { + Exception ex = new Exception(); + IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); + Exception exceptionFromCacheProvider = null; - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - // error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - // failed to put it in the cache - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public async Task Should_execute_oncacheget_after_got_from_cache() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - - const string operationKey = "SomeOperationKey"; - string keyPassedToDelegate = null; - - Context contextToExecute = new Context(operationKey); - Context contextPassedToDelegate = null; - - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; - - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - - bool delegateExecuted = false; - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, contextToExecute)) - .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - - contextPassedToDelegate.Should().BeSameAs(contextToExecute); - keyPassedToDelegate.Should().Be(operationKey); - } - - [Fact] - public async Task Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() - { - const string valueToReturn = "valueToReturn"; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + // error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); - Context contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + // failed to put it in the cache + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + [Fact] + public async Task Should_execute_oncacheget_after_got_from_cache() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + + const string operationKey = "SomeOperationKey"; + string keyPassedToDelegate = null; + + Context contextToExecute = new Context(operationKey); + Context contextPassedToDelegate = null; + + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + + bool delegateExecuted = false; + (await cache.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, contextToExecute)) + .Should().Be(valueToReturnFromCache); + delegateExecuted.Should().BeFalse(); + + contextPassedToDelegate.Should().BeSameAs(contextToExecute); + keyPassedToDelegate.Should().Be(operationKey); + } - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + [Fact] + public async Task Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() + { + const string valueToReturn = "valueToReturn"; - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); + Context contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); - keyPassedToOnCachePut.Should().Be(operationKey); - } + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - [Fact] - public async Task Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); - Context contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); + keyPassedToOnCachePut.Should().Be(operationKey); + } - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + [Fact] + public async Task Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); + Context contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - contextPassedToOnCachePut.Should().BeNull(); - keyPassedToOnCachePut.Should().BeNull(); - } + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - [Fact] - public async Task Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() - { - string valueToReturn = Guid.NewGuid().ToString(); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - bool onCacheMissExecuted = false; - Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); - var cache = Policy.CacheAsync(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); + contextPassedToOnCachePut.Should().BeNull(); + keyPassedToOnCachePut.Should().BeNull(); + } + + [Fact] + public async Task Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() + { + string valueToReturn = Guid.NewGuid().ToString(); - (await cache.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; - return valueToReturn; - } /*, no operation key */)) - .Should().Be(valueToReturn); + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - onCacheMissExecuted.Should().BeFalse(); - } + bool onCacheMissExecuted = false; + Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; - #endregion + var cache = Policy.CacheAsync(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); - public void Dispose() + (await cache.ExecuteAsync(async () => { - SystemClock.Reset(); - } + await TaskHelper.EmptyTask; + return valueToReturn; + } /*, no operation key */)) + .Should().Be(valueToReturn); + + onCacheMissExecuted.Should().BeFalse(); + } + + #endregion + + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/Caching/CacheSpecs.cs b/src/Polly.Specs/Caching/CacheSpecs.cs index 3db2202c5e3..bcad643baf9 100644 --- a/src/Polly.Specs/Caching/CacheSpecs.cs +++ b/src/Polly.Specs/Caching/CacheSpecs.cs @@ -8,693 +8,692 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class CacheSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class CacheSpecs : IDisposable + #region Configuration + + [Fact] + public void Should_throw_when_cache_provider_is_null() { - #region Configuration + ISyncCacheProvider cacheProvider = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - [Fact] - public void Should_throw_when_cache_provider_is_null() - { - ISyncCacheProvider cacheProvider = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.Cache(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.Cache(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } - - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } + + #endregion + + #region Caching behaviours - #endregion + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; + + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - #region Caching behaviours + bool delegateExecuted = false; - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + cache.Execute(_ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + delegateExecuted.Should().BeFalse(); + } - bool delegateExecuted = false; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - delegateExecuted.Should().BeFalse(); - } + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + TimeSpan ttl = TimeSpan.FromMinutes(30); + CachePolicy cache = Policy.Cache(stubCacheProvider, ttl); - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + int delegateInvocations = 0; + Func func = _ => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateInvocations++; + return valueToReturn; + }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - TimeSpan ttl = TimeSpan.FromMinutes(30); - CachePolicy cache = Policy.Cache(stubCacheProvider, ttl); + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + // First execution should execute delegate and put result in the cache. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - int delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - // First execution should execute delegate and put result in the cache. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); + [Fact] + public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); - [Fact] - public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + int delegateInvocations = 0; + Func func = _ => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateInvocations++; + return valueToReturn; + }; - CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - int delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + [Fact] + public void Should_allow_custom_FuncCacheKeyStrategy() + { + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + object person1 = new object(); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + object person2 = new object(); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - [Fact] - public void Should_allow_custom_FuncCacheKeyStrategy() - { - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + bool funcExecuted = false; + Func func = _ => { funcExecuted = true; return new object(); }; - object person1 = new object(); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - object person2 = new object(); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - bool funcExecuted = false; - Func func = _ => { funcExecuted = true; return new object(); }; + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + [Fact] + public void Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - [Fact] - public void Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + object person1 = new object(); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + object person2 = new object(); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + bool funcExecuted = false; + Func func = _ => { funcExecuted = true; return new object(); }; - object person1 = new object(); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - object person2 = new object(); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - bool funcExecuted = false; - Func func = _ => { funcExecuted = true; return new object(); }; + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + #endregion - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + #region Caching behaviours, default(TResult) - #endregion + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - #region Caching behaviours, default(TResult) + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + bool delegateExecuted = false; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - bool delegateExecuted = false; + delegateExecuted.Should().BeFalse(); + } - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() - { - ResultPrimitive valueToReturnFromCache = default; - ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + bool delegateExecuted = false; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - bool delegateExecuted = false; + delegateExecuted.Should().BeFalse(); + } - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + #endregion - delegateExecuted.Should().BeFalse(); - } + #region Non-generic CachePolicy in non-generic PolicyWrap - #endregion + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - #region Non-generic CachePolicy in non-generic PolicyWrap + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + PolicyWrap wrap = Policy.Wrap(cache, noop); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + + bool delegateExecuted = false; + + wrap.Execute(_ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - PolicyWrap wrap = Policy.Wrap(cache, noop); + delegateExecuted.Should().BeFalse(); + } - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - bool delegateExecuted = false; + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + PolicyWrap wrap = Policy.Wrap(noop, cache); - wrap.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - delegateExecuted.Should().BeFalse(); - } + bool delegateExecuted = false; - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + wrap.Execute(_ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - PolicyWrap wrap = Policy.Wrap(noop, cache); + delegateExecuted.Should().BeFalse(); + } - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - bool delegateExecuted = false; + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + PolicyWrap wrap = Policy.Wrap(noop, cache, noop); - wrap.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - delegateExecuted.Should().BeFalse(); - } + bool delegateExecuted = false; - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + wrap.Execute(_ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - PolicyWrap wrap = Policy.Wrap(noop, cache, noop); + delegateExecuted.Should().BeFalse(); + } - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + #endregion - bool delegateExecuted = false; + #region No-op pass-through behaviour - wrap.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); - - delegateExecuted.Should().BeFalse(); - } - - #endregion + [Fact] + public void Should_always_execute_delegate_if_execution_key_not_set() + { + string valueToReturn = Guid.NewGuid().ToString(); - #region No-op pass-through behaviour + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - [Fact] - public void Should_always_execute_delegate_if_execution_key_not_set() + int delegateInvocations = 0; + Func func = () => { - string valueToReturn = Guid.NewGuid().ToString(); + delegateInvocations++; + return valueToReturn; + }; - CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - int delegateInvocations = 0; - Func func = () => - { - delegateInvocations++; - return valueToReturn; - }; + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + [Fact] + public void Should_always_execute_delegate_if_execution_is_void_returning() + { + string operationKey = "SomeKey"; - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - [Fact] - public void Should_always_execute_delegate_if_execution_is_void_returning() - { - string operationKey = "SomeKey"; + int delegateInvocations = 0; + Action action = _ => { delegateInvocations++; }; - CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + cache.Execute(action, new Context(operationKey)); + delegateInvocations.Should().Be(1); - int delegateInvocations = 0; - Action action = _ => { delegateInvocations++; }; + cache.Execute(action, new Context(operationKey)); + delegateInvocations.Should().Be(2); + } - cache.Execute(action, new Context(operationKey)); - delegateInvocations.Should().Be(1); + #endregion - cache.Execute(action, new Context(operationKey)); - delegateInvocations.Should().Be(2); - } + #region Cancellation - #endregion + [Fact] + public void Should_honour_cancellation_even_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; + + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - #region Cancellation + CancellationTokenSource tokenSource = new CancellationTokenSource(); - [Fact] - public void Should_honour_cancellation_even_if_prior_execution_has_cached() + int delegateInvocations = 0; + Func func = (_, _) => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + return valueToReturn; + }; - CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - CancellationTokenSource tokenSource = new CancellationTokenSource(); + tokenSource.Cancel(); - int delegateInvocations = 0; - Func func = (_, _) => - { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - return valueToReturn; - }; + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + delegateInvocations.Should().Be(1); + } - cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + [Fact] + public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - tokenSource.Cancel(); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - delegateInvocations.Should().Be(1); - } + CancellationTokenSource tokenSource = new CancellationTokenSource(); - [Fact] - public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + Func func = (_, ct) => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + return valueToReturn; + }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); - CancellationTokenSource tokenSource = new CancellationTokenSource(); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } - Func func = (_, ct) => - { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - return valueToReturn; - }; + #endregion - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); + #region Policy hooks - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + [Fact] + public void Should_call_onError_delegate_if_cache_get_errors() + { + Exception ex = new Exception(); + ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); - #endregion + Exception exceptionFromCacheProvider = null; - #region Policy hooks + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - [Fact] - public void Should_call_onError_delegate_if_cache_get_errors() - { - Exception ex = new Exception(); - ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - Exception exceptionFromCacheProvider = null; + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + bool delegateExecuted = false; - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + // Even though value is in cache, get will error; so value is returned from execution. + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromExecution); + delegateExecuted.Should().BeTrue(); - bool delegateExecuted = false; + // And error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); + } + [Fact] + public void Should_call_onError_delegate_if_cache_put_errors() + { + Exception ex = new Exception(); + ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); - // Even though value is in cache, get will error; so value is returned from execution. - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromExecution); - delegateExecuted.Should().BeTrue(); + Exception exceptionFromCacheProvider = null; - // And error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); - } + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - [Fact] - public void Should_call_onError_delegate_if_cache_put_errors() - { - Exception ex = new Exception(); - ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - Exception exceptionFromCacheProvider = null; + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + // error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + // failed to put it in the cache + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + } - // error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); + [Fact] + public void Should_execute_oncacheget_after_got_from_cache() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; - // failed to put it in the cache - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); + const string operationKey = "SomeOperationKey"; + string keyPassedToDelegate = null; - } + Context contextToExecute = new Context(operationKey); + Context contextPassedToDelegate = null; - [Fact] - public void Should_execute_oncacheget_after_got_from_cache() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - - const string operationKey = "SomeOperationKey"; - string keyPassedToDelegate = null; - - Context contextToExecute = new Context(operationKey); - Context contextPassedToDelegate = null; - - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; - - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - - bool delegateExecuted = false; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, contextToExecute) - .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - - contextPassedToDelegate.Should().BeSameAs(contextToExecute); - keyPassedToDelegate.Should().Be(operationKey); - } - - [Fact] - public void Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() - { - const string valueToReturn = "valueToReturn"; + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - Context contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + bool delegateExecuted = false; + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, contextToExecute) + .Should().Be(valueToReturnFromCache); + delegateExecuted.Should().BeFalse(); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + contextPassedToDelegate.Should().BeSameAs(contextToExecute); + keyPassedToDelegate.Should().Be(operationKey); + } - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + [Fact] + public void Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() + { + const string valueToReturn = "valueToReturn"; - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); + Context contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); - keyPassedToOnCacheMiss.Should().Be(operationKey); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); - keyPassedToOnCachePut.Should().Be(operationKey); - } + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public void Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; + cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - Context contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); + keyPassedToOnCacheMiss.Should().Be(operationKey); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); + keyPassedToOnCachePut.Should().Be(operationKey); + } - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + [Fact] + public void Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); + Context contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); - keyPassedToOnCacheMiss.Should().Be(operationKey); + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - contextPassedToOnCachePut.Should().BeNull(); - keyPassedToOnCachePut.Should().BeNull(); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - [Fact] - public void Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() - { - string valueToReturn = Guid.NewGuid().ToString(); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); - bool onCacheMissExecuted = false; - Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; + contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); + keyPassedToOnCacheMiss.Should().Be(operationKey); - CachePolicy cache = Policy.Cache(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); + contextPassedToOnCachePut.Should().BeNull(); + keyPassedToOnCachePut.Should().BeNull(); + } - cache.Execute(() => valueToReturn /*, no operation key */).Should().Be(valueToReturn); + [Fact] + public void Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() + { + string valueToReturn = Guid.NewGuid().ToString(); - onCacheMissExecuted.Should().BeFalse(); - } + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - #endregion + bool onCacheMissExecuted = false; + Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; - public void Dispose() - { - SystemClock.Reset(); - } + CachePolicy cache = Policy.Cache(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); + + cache.Execute(() => valueToReturn /*, no operation key */).Should().Be(valueToReturn); + + onCacheMissExecuted.Should().BeFalse(); + } + + #endregion + + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs index d7f038e65c4..21aa1bc4f92 100644 --- a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs @@ -8,494 +8,493 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching -{ - [Collection(Constants.SystemClockDependentTestCollection)] - public class CacheTResultAsyncSpecs : IDisposable - { - #region Configuration +namespace Polly.Specs.Caching; - [Fact] - public void Should_throw_when_cache_provider_is_null() - { - IAsyncCacheProvider cacheProvider = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } +[Collection(Constants.SystemClockDependentTestCollection)] +public class CacheTResultAsyncSpecs : IDisposable +{ + #region Configuration - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } - - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } - #endregion + [Fact] + public void Should_throw_when_cache_provider_is_null() + { + IAsyncCacheProvider cacheProvider = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - #region Caching behaviours + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } + #endregion - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + #region Caching behaviours - bool delegateExecuted = false; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - delegateExecuted.Should().BeFalse(); - } + bool delegateExecuted = false; - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + (await cache.ExecuteAsync(async _ => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + delegateExecuted.Should().BeFalse(); + } - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - TimeSpan ttl = TimeSpan.FromMinutes(30); - var cache = Policy.CacheAsync(stubCacheProvider, ttl); - - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); - - int delegateInvocations = 0; - Func> func = async _ => - { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; - - DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - - // First execution should execute delegate and put result in the cache. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } - - [Fact] - public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + TimeSpan ttl = TimeSpan.FromMinutes(30); + var cache = Policy.CacheAsync(stubCacheProvider, ttl); - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + int delegateInvocations = 0; + Func> func = async _ => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; + + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; + + // First execution should execute delegate and put result in the cache. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); + + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + [Fact] + public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); - int delegateInvocations = 0; - Func> func = async _ => - { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - [Fact] - public async Task Should_allow_custom_FuncCacheKeyStrategy() + int delegateInvocations = 0; + Func> func = async _ => { - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - bool funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_allow_custom_FuncCacheKeyStrategy() + { + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - [Fact] - public async Task Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - //var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - var cache = Policy.CacheAsync(stubCacheProvider.AsyncFor(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + bool funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - bool funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + [Fact] + public async Task Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + //var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + var cache = Policy.CacheAsync(stubCacheProvider.AsyncFor(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - #endregion + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - #region Caching behaviours, default(TResult) + bool funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + #endregion - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + #region Caching behaviours, default(TResult) - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - bool delegateExecuted = false; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - delegateExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + bool delegateExecuted = false; - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (await cache.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + delegateExecuted.Should().BeFalse(); + } - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() - { - ResultPrimitive valueToReturnFromCache = default; - ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - bool delegateExecuted = false; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - delegateExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; - #endregion + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - #region Generic CachePolicy in PolicyWrap + bool delegateExecuted = false; - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + (await cache.ExecuteAsync(async _ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = cache.WrapAsync(noop); + delegateExecuted.Should().BeFalse(); + } - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + #endregion - bool delegateExecuted = false; + #region Generic CachePolicy in PolicyWrap - (await wrap.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = cache.WrapAsync(noop); - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = noop.WrapAsync(cache); + bool delegateExecuted = false; - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + (await wrap.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - bool delegateExecuted = false; + delegateExecuted.Should().BeFalse(); + } - (await wrap.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = noop.WrapAsync(cache); - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(noop, cache, noop); + bool delegateExecuted = false; - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + (await wrap.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - bool delegateExecuted = false; + delegateExecuted.Should().BeFalse(); + } - (await wrap.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(noop, cache, noop); - #endregion + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - #region No-op pass-through behaviour + bool delegateExecuted = false; - [Fact] - public async Task Should_always_execute_delegate_if_execution_key_not_set() + (await wrap.ExecuteAsync(async _ => { - string valueToReturn = Guid.NewGuid().ToString(); + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + delegateExecuted.Should().BeFalse(); + } - int delegateInvocations = 0; - Func> func = async () => { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + #endregion - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + #region No-op pass-through behaviour - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + [Fact] + public async Task Should_always_execute_delegate_if_execution_key_not_set() + { + string valueToReturn = Guid.NewGuid().ToString(); - #endregion + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - #region Cancellation + int delegateInvocations = 0; + Func> func = async () => { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - [Fact] - public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - CancellationTokenSource tokenSource = new CancellationTokenSource(); + #endregion - int delegateInvocations = 0; - Func> func = async (_, _) => - { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + #region Cancellation - (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + [Fact] + public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - tokenSource.Cancel(); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().ThrowAsync(); - delegateInvocations.Should().Be(1); - } + CancellationTokenSource tokenSource = new CancellationTokenSource(); - [Fact] - public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + int delegateInvocations = 0; + Func> func = async (_, _) => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - CancellationTokenSource tokenSource = new CancellationTokenSource(); + tokenSource.Cancel(); - Func> func = async (_, ct) => - { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - await TaskHelper.EmptyTask; - return valueToReturn; - }; + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); + delegateInvocations.Should().Be(1); + } - await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().ThrowAsync(); + [Fact] + public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - #endregion + CancellationTokenSource tokenSource = new CancellationTokenSource(); - public void Dispose() + Func> func = async (_, ct) => { - SystemClock.Reset(); - } + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + await TaskHelper.EmptyTask; + return valueToReturn; + }; + + await cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().ThrowAsync(); + + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } + + #endregion + + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/CacheTResultSpecs.cs b/src/Polly.Specs/Caching/CacheTResultSpecs.cs index cd0a38c4ea1..161d2916ef2 100644 --- a/src/Polly.Specs/Caching/CacheTResultSpecs.cs +++ b/src/Polly.Specs/Caching/CacheTResultSpecs.cs @@ -8,484 +8,483 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class CacheTResultSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class CacheTResultSpecs : IDisposable + #region Configuration + + [Fact] + public void Should_throw_when_cache_provider_is_null() { - #region Configuration + ISyncCacheProvider cacheProvider = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - [Fact] - public void Should_throw_when_cache_provider_is_null() - { - ISyncCacheProvider cacheProvider = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.Cache(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.Cache(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } + #endregion + + #region Caching behaviours - #endregion + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - #region Caching behaviours + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + bool delegateExecuted = false; + + cache.Execute(_ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + delegateExecuted.Should().BeFalse(); + } - bool delegateExecuted = false; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - delegateExecuted.Should().BeFalse(); - } + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + TimeSpan ttl = TimeSpan.FromMinutes(30); + CachePolicy cache = Policy.Cache(stubCacheProvider, ttl); - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + int delegateInvocations = 0; + Func func = _ => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - TimeSpan ttl = TimeSpan.FromMinutes(30); - CachePolicy cache = Policy.Cache(stubCacheProvider, ttl); - - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); - - int delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; - - DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - - // First execution should execute delegate and put result in the cache. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } - - [Fact] - public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateInvocations++; + return valueToReturn; + }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + // First execution should execute delegate and put result in the cache. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - int delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; + [Fact] + public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - [Fact] - public void Should_allow_custom_FuncICacheKeyStrategy() - { + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - bool funcExecuted = false; - Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + int delegateInvocations = 0; + Func func = _ => + { + delegateInvocations++; + return valueToReturn; + }; - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - [Fact] - public void Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - CachePolicy cache = Policy.Cache(stubCacheProvider.For(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + [Fact] + public void Should_allow_custom_FuncICacheKeyStrategy() + { - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - bool funcExecuted = false; - Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + bool funcExecuted = false; + Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - #endregion + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - #region Caching behaviours, default(TResult) + [Fact] + public void Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + CachePolicy cache = Policy.Cache(stubCacheProvider.For(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + bool funcExecuted = false; + Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + #endregion - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + #region Caching behaviours, default(TResult) - bool delegateExecuted = false; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - delegateExecuted.Should().BeFalse(); - } + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + bool delegateExecuted = false; - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + cache.Execute(_ => { - ResultPrimitive valueToReturnFromCache = default; - ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + delegateExecuted.Should().BeFalse(); + } - bool delegateExecuted = false; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - delegateExecuted.Should().BeFalse(); - } + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - #endregion + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } + + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; - #region Generic CachePolicy in PolicyWrap + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + bool delegateExecuted = false; + + cache.Execute(_ => { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - PolicyWrap wrap = cache.Wrap(noop); + delegateExecuted.Should().BeFalse(); + } - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + #endregion - bool delegateExecuted = false; + #region Generic CachePolicy in PolicyWrap - wrap.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + PolicyWrap wrap = cache.Wrap(noop); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - PolicyWrap wrap = noop.Wrap(cache); + bool delegateExecuted = false; - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + wrap.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - bool delegateExecuted = false; + delegateExecuted.Should().BeFalse(); + } - wrap.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + PolicyWrap wrap = noop.Wrap(cache); - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - PolicyWrap wrap = Policy.Wrap(noop, cache, noop); + bool delegateExecuted = false; - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + wrap.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - bool delegateExecuted = false; + delegateExecuted.Should().BeFalse(); + } - wrap.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - delegateExecuted.Should().BeFalse(); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + PolicyWrap wrap = Policy.Wrap(noop, cache, noop); - #endregion + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - #region No-op pass-through behaviour + bool delegateExecuted = false; - [Fact] - public void Should_always_execute_delegate_if_execution_key_not_set() + wrap.Execute(_ => { - string valueToReturn = Guid.NewGuid().ToString(); + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - - int delegateInvocations = 0; - Func func = () => - { - delegateInvocations++; - return valueToReturn; - }; + delegateExecuted.Should().BeFalse(); + } - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + #endregion - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + #region No-op pass-through behaviour - #endregion + [Fact] + public void Should_always_execute_delegate_if_execution_key_not_set() + { + string valueToReturn = Guid.NewGuid().ToString(); - #region Cancellation + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - [Fact] - public void Should_honour_cancellation_even_if_prior_execution_has_cached() + int delegateInvocations = 0; + Func func = () => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + delegateInvocations++; + return valueToReturn; + }; + + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - CancellationTokenSource tokenSource = new CancellationTokenSource(); + #endregion - int delegateInvocations = 0; - Func func = (_, _) => - { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - return valueToReturn; - }; + #region Cancellation - cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + [Fact] + public void Should_honour_cancellation_even_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - tokenSource.Cancel(); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - delegateInvocations.Should().Be(1); - } + CancellationTokenSource tokenSource = new CancellationTokenSource(); - [Fact] - public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + int delegateInvocations = 0; + Func func = (_, _) => { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + return valueToReturn; + }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - CancellationTokenSource tokenSource = new CancellationTokenSource(); + tokenSource.Cancel(); - Func func = (_, ct) => - { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - return valueToReturn; - }; + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + delegateInvocations.Should().Be(1); + } - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); + [Fact] + public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - #endregion + CancellationTokenSource tokenSource = new CancellationTokenSource(); - public void Dispose() + Func func = (_, ct) => { - SystemClock.Reset(); - } + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + return valueToReturn; + }; + + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } + + #endregion + + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/Caching/ContextualTtlSpecs.cs b/src/Polly.Specs/Caching/ContextualTtlSpecs.cs index 0fc725606ba..a0318b99931 100644 --- a/src/Polly.Specs/Caching/ContextualTtlSpecs.cs +++ b/src/Polly.Specs/Caching/ContextualTtlSpecs.cs @@ -4,50 +4,49 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +public class ContextualTtlSpecs { - public class ContextualTtlSpecs + [Fact] + public void Should_return_zero_if_no_value_set_on_context() + { + new ContextualTtl().GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); + } + + [Fact] + public void Should_return_zero_if_invalid_value_set_on_context() { - [Fact] - public void Should_return_zero_if_no_value_set_on_context() - { - new ContextualTtl().GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); - } - - [Fact] - public void Should_return_zero_if_invalid_value_set_on_context() - { - Dictionary contextData = new Dictionary(); - contextData[ContextualTtl.TimeSpanKey] = new object(); - - Context context = new Context(String.Empty, contextData); - new ContextualTtl().GetTtl(context, null).Timespan.Should().Be(TimeSpan.Zero); - } - - [Fact] - public void Should_return_value_set_on_context() - { - TimeSpan ttl = TimeSpan.FromSeconds(30); - Dictionary contextData = new Dictionary(); - contextData[ContextualTtl.TimeSpanKey] = ttl; - - Context context = new Context(String.Empty, contextData); - Ttl gotTtl = new ContextualTtl().GetTtl(context, null); - gotTtl.Timespan.Should().Be(ttl); - gotTtl.SlidingExpiration.Should().BeFalse(); - } - - [Fact] - public void Should_return_negative_value_set_on_context() - { - TimeSpan ttl = TimeSpan.FromTicks(-1); - Dictionary contextData = new Dictionary(); - contextData[ContextualTtl.TimeSpanKey] = ttl; - - Context context = new Context(String.Empty, contextData); - Ttl gotTtl = new ContextualTtl().GetTtl(context, null); - gotTtl.Timespan.Should().Be(ttl); - gotTtl.SlidingExpiration.Should().BeFalse(); - } + Dictionary contextData = new Dictionary(); + contextData[ContextualTtl.TimeSpanKey] = new object(); + + Context context = new Context(String.Empty, contextData); + new ContextualTtl().GetTtl(context, null).Timespan.Should().Be(TimeSpan.Zero); + } + + [Fact] + public void Should_return_value_set_on_context() + { + TimeSpan ttl = TimeSpan.FromSeconds(30); + Dictionary contextData = new Dictionary(); + contextData[ContextualTtl.TimeSpanKey] = ttl; + + Context context = new Context(String.Empty, contextData); + Ttl gotTtl = new ContextualTtl().GetTtl(context, null); + gotTtl.Timespan.Should().Be(ttl); + gotTtl.SlidingExpiration.Should().BeFalse(); + } + + [Fact] + public void Should_return_negative_value_set_on_context() + { + TimeSpan ttl = TimeSpan.FromTicks(-1); + Dictionary contextData = new Dictionary(); + contextData[ContextualTtl.TimeSpanKey] = ttl; + + Context context = new Context(String.Empty, contextData); + Ttl gotTtl = new ContextualTtl().GetTtl(context, null); + gotTtl.Timespan.Should().Be(ttl); + gotTtl.SlidingExpiration.Should().BeFalse(); } } diff --git a/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs b/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs index bb4205279d6..53b746e66db 100644 --- a/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs +++ b/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs @@ -2,19 +2,18 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +public class DefaultCacheKeyStrategySpecs { - public class DefaultCacheKeyStrategySpecs + [Fact] + public void Should_return_Context_OperationKey_as_cache_key() { - [Fact] - public void Should_return_Context_OperationKey_as_cache_key() - { - string operationKey = "SomeKey"; + string operationKey = "SomeKey"; - Context context = new Context(operationKey); + Context context = new Context(operationKey); - DefaultCacheKeyStrategy.Instance.GetCacheKey(context) - .Should().Be(operationKey); - } + DefaultCacheKeyStrategy.Instance.GetCacheKey(context) + .Should().Be(operationKey); } } diff --git a/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs b/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs index d573519f39e..2acc731cec2 100644 --- a/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs @@ -8,62 +8,61 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class GenericCacheProviderAsyncSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class GenericCacheProviderAsyncSpecs : IDisposable + [Fact] + public async Task Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() { - [Fact] - public async Task Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() - { - const string operationKey = "SomeOperationKey"; - - bool onErrorCalled = false; - Action onError = (_, _, _) => { onErrorCalled = true; }; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); + bool onErrorCalled = false; + Action onError = (_, _, _) => { onErrorCalled = true; }; - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - ResultPrimitive result = await cache.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - return ResultPrimitive.Substitute; - }, new Context(operationKey)); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - onErrorCalled.Should().BeFalse(); - } - - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() + ResultPrimitive result = await cache.ExecuteAsync(async _ => { - const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; - const string operationKey = "SomeOperationKey"; + await TaskHelper.EmptyTask; + return ResultPrimitive.Substitute; + }, new Context(operationKey)); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + onErrorCalled.Should().BeFalse(); + } - (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() + { + const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; + const string operationKey = "SomeOperationKey"; - (await cache.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - return ResultPrimitive.Substitute; - }, new Context(operationKey))).Should().Be(valueToReturn); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - public void Dispose() + (await cache.ExecuteAsync(async _ => { - SystemClock.Reset(); - } + await TaskHelper.EmptyTask; + return ResultPrimitive.Substitute; + }, new Context(operationKey))).Should().Be(valueToReturn); + + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } + + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs b/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs index 3ae137576cd..19b1c368bd2 100644 --- a/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs +++ b/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs @@ -6,56 +6,55 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class GenericCacheProviderSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class GenericCacheProviderSpecs : IDisposable + [Fact] + public void Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() { - [Fact] - public void Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() - { - const string operationKey = "SomeOperationKey"; + const string operationKey = "SomeOperationKey"; - bool onErrorCalled = false; - Action onError = (_, _, _) => { onErrorCalled = true; }; + bool onErrorCalled = false; + Action onError = (_, _, _) => { onErrorCalled = true; }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - ResultPrimitive result = cache.Execute(_ => ResultPrimitive.Substitute, new Context(operationKey)); + ResultPrimitive result = cache.Execute(_ => ResultPrimitive.Substitute, new Context(operationKey)); - onErrorCalled.Should().BeFalse(); - } + onErrorCalled.Should().BeFalse(); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() - { - const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() + { + const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs index b3edd1da1c4..815834df3a4 100644 --- a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs +++ b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs @@ -4,60 +4,59 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +public class RelativeTtlSpecs { - public class RelativeTtlSpecs + [Fact] + public void Should_throw_when_timespan_is_less_than_zero() { - [Fact] - public void Should_throw_when_timespan_is_less_than_zero() - { - Action configure = () => new RelativeTtl(TimeSpan.FromMilliseconds(-1)); + Action configure = () => new RelativeTtl(TimeSpan.FromMilliseconds(-1)); - configure.Should().Throw().And.ParamName.Should().Be("ttl"); - } + configure.Should().Throw().And.ParamName.Should().Be("ttl"); + } - [Fact] - public void Should_not_throw_when_timespan_is_zero() - { - Action configure = () => new RelativeTtl(TimeSpan.Zero); + [Fact] + public void Should_not_throw_when_timespan_is_zero() + { + Action configure = () => new RelativeTtl(TimeSpan.Zero); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_allow_timespan_max_value() - { - Action configure = () => new RelativeTtl(TimeSpan.MaxValue); + [Fact] + public void Should_allow_timespan_max_value() + { + Action configure = () => new RelativeTtl(TimeSpan.MaxValue); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_return_configured_timespan() - { - TimeSpan ttl = TimeSpan.FromSeconds(30); + [Fact] + public void Should_return_configured_timespan() + { + TimeSpan ttl = TimeSpan.FromSeconds(30); - RelativeTtl ttlStrategy = new RelativeTtl(ttl); + RelativeTtl ttlStrategy = new RelativeTtl(ttl); - Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().BeCloseTo(ttl, TimeSpan.Zero); - retrieved.SlidingExpiration.Should().BeFalse(); - } + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + retrieved.Timespan.Should().BeCloseTo(ttl, TimeSpan.Zero); + retrieved.SlidingExpiration.Should().BeFalse(); + } - [Fact] - public void Should_return_configured_timespan_from_time_requested() - { - DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); - TimeSpan ttl = TimeSpan.FromSeconds(30); - TimeSpan delay = TimeSpan.FromSeconds(5); + [Fact] + public void Should_return_configured_timespan_from_time_requested() + { + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); + TimeSpan ttl = TimeSpan.FromSeconds(30); + TimeSpan delay = TimeSpan.FromSeconds(5); - RelativeTtl ttlStrategy = new RelativeTtl(ttl); + RelativeTtl ttlStrategy = new RelativeTtl(ttl); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(delay); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(delay); - Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().BeCloseTo(ttl, TimeSpan.Zero); - retrieved.SlidingExpiration.Should().BeFalse(); - } + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + retrieved.Timespan.Should().BeCloseTo(ttl, TimeSpan.Zero); + retrieved.SlidingExpiration.Should().BeFalse(); } } diff --git a/src/Polly.Specs/Caching/ResultTtlSpecs.cs b/src/Polly.Specs/Caching/ResultTtlSpecs.cs index 2ed9a2633f5..eb2f634a5ed 100644 --- a/src/Polly.Specs/Caching/ResultTtlSpecs.cs +++ b/src/Polly.Specs/Caching/ResultTtlSpecs.cs @@ -3,67 +3,66 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +public class ResultTtlSpecs { - public class ResultTtlSpecs + [Fact] + public void Should_throw_when_func_is_null() { - [Fact] - public void Should_throw_when_func_is_null() - { - Action configure = () => new ResultTtl((Func)null); + Action configure = () => new ResultTtl((Func)null); - configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); - } + configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); + } - [Fact] - public void Should_throw_when_func_is_null_using_context() - { - Action configure = () => new ResultTtl((Func)null); + [Fact] + public void Should_throw_when_func_is_null_using_context() + { + Action configure = () => new ResultTtl((Func)null); - configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); - } + configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); + } - [Fact] - public void Should_not_throw_when_func_is_set() - { - Action configure = () => new ResultTtl(_ => new Ttl()); + [Fact] + public void Should_not_throw_when_func_is_set() + { + Action configure = () => new ResultTtl(_ => new Ttl()); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_func_is_set_using_context() - { - Action configure = () => new ResultTtl((_, _) => new Ttl()); + [Fact] + public void Should_not_throw_when_func_is_set_using_context() + { + Action configure = () => new ResultTtl((_, _) => new Ttl()); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_return_func_result() - { - TimeSpan ttl = TimeSpan.FromMinutes(1); - Func func = result => new Ttl(result.Ttl); + [Fact] + public void Should_return_func_result() + { + TimeSpan ttl = TimeSpan.FromMinutes(1); + Func func = result => new Ttl(result.Ttl); - ResultTtl ttlStrategy = new ResultTtl(func); + ResultTtl ttlStrategy = new ResultTtl(func); - Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }); - retrieved.Timespan.Should().Be(ttl); - retrieved.SlidingExpiration.Should().BeFalse(); - } + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }); + retrieved.Timespan.Should().Be(ttl); + retrieved.SlidingExpiration.Should().BeFalse(); + } - [Fact] - public void Should_return_func_result_using_context() - { - const string specialKey = "specialKey"; + [Fact] + public void Should_return_func_result_using_context() + { + const string specialKey = "specialKey"; - TimeSpan ttl = TimeSpan.FromMinutes(1); - Func func = (context, result) => context.OperationKey == specialKey ? new Ttl(TimeSpan.Zero) : new Ttl(result.Ttl); + TimeSpan ttl = TimeSpan.FromMinutes(1); + Func func = (context, result) => context.OperationKey == specialKey ? new Ttl(TimeSpan.Zero) : new Ttl(result.Ttl); - ResultTtl ttlStrategy = new ResultTtl(func); + ResultTtl ttlStrategy = new ResultTtl(func); - ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }).Timespan.Should().Be(ttl); - ttlStrategy.GetTtl(new Context(specialKey), new { Ttl = ttl }).Timespan.Should().Be(TimeSpan.Zero); - } + ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }).Timespan.Should().Be(ttl); + ttlStrategy.GetTtl(new Context(specialKey), new { Ttl = ttl }).Timespan.Should().Be(TimeSpan.Zero); } } diff --git a/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs b/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs index 29c1da2341f..730baa2f61a 100644 --- a/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs @@ -7,448 +7,447 @@ using Polly.Specs.Helpers.Caching; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +public class AsyncSerializingCacheProviderSpecs { - public class AsyncSerializingCacheProviderSpecs + #region Object-to-TSerialized serializer + + [Fact] + public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + StubSerializer stubObjectSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => s.Original + ); + + Action configure = () => new AsyncSerializingCacheProvider(null, stubObjectSerializer); + + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Single_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Single_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + string key = "some key"; + + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + string key = "some key"; + + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + #endregion + + #region TResult-to-TSerialized serializer + + [Fact] + public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => s.Original + ); + + Action configure = () => new AsyncSerializingCacheProvider>(null, stubTResultSerializer); + + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Double_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Double_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + string key = "some key"; + + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() { - #region Object-to-TSerialized serializer - - [Fact] - public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - StubSerializer stubObjectSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => s.Original - ); - - Action configure = () => new AsyncSerializingCacheProvider(null, stubObjectSerializer); - - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); - } - - [Fact] - public void Single_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Single_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - string key = "some key"; - - AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - string key = "some key"; - - AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - #endregion - - #region TResult-to-TSerialized serializer - - [Fact] - public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => s.Original - ); - - Action configure = () => new AsyncSerializingCacheProvider>(null, stubTResultSerializer); - - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); - } - - [Fact] - public void Double_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Double_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - string key = "some key"; - - AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - AsyncSerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - string key = "some key"; - - AsyncSerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - AsyncSerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - AsyncSerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - #endregion + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); } -} \ No newline at end of file + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + AsyncSerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + string key = "some key"; + + AsyncSerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + AsyncSerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + AsyncSerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); + } + + #endregion +} diff --git a/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs b/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs index 5a1a198fb8f..28b9627536e 100644 --- a/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs +++ b/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs @@ -5,450 +5,449 @@ using Polly.Specs.Helpers.Caching; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +public class SerializingCacheProviderSpecs { - public class SerializingCacheProviderSpecs + #region Object-to-TSerialized serializer + + [Fact] + public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + StubSerializer stubObjectSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => s.Original + ); + + Action configure = () => new SerializingCacheProvider(null, stubObjectSerializer); + + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Single_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Single_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().For().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o);}, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + #endregion + + #region TResult-to-TSerialized serializer + + [Fact] + public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => s.Original + ); + + Action configure = () => new SerializingCacheProvider>(null, stubTResultSerializer); + + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Double_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Double_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().For().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() { - #region Object-to-TSerialized serializer - - [Fact] - public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - StubSerializer stubObjectSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => s.Original - ); - - Action configure = () => new SerializingCacheProvider(null, stubObjectSerializer); - - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); - } - - [Fact] - public void Single_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Single_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().For().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o);}, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - string key = "some key"; - - SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - - SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - string key = "some key"; - - SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = new object(); - string key = "some key"; - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - - SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - #endregion - - #region TResult-to-TSerialized serializer - - [Fact] - public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - StubSerializer> stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original - ); - - Action configure = () => new SerializingCacheProvider>(null, stubTResultSerializer); - - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); - } - - [Fact] - public void Double_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Double_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().For().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - string key = "some key"; - - SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - SerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() - { - bool serializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - StubCacheProvider stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - string key = "some key"; - - SerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = ResultPrimitive.Good; - string key = "some key"; - - SerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - bool deserializeInvoked = false; - StubSerializer> stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - string key = "some key"; - - stubCacheProvider.TryGet(key).Item2.Should().BeNull(); - - SerializingCacheProvider> serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - #endregion + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.TryGet(key).Item2.Should().BeNull(); + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); + } + + #endregion } diff --git a/src/Polly.Specs/Caching/SlidingTtlSpecs.cs b/src/Polly.Specs/Caching/SlidingTtlSpecs.cs index 1b726877c1a..5539b9361f4 100644 --- a/src/Polly.Specs/Caching/SlidingTtlSpecs.cs +++ b/src/Polly.Specs/Caching/SlidingTtlSpecs.cs @@ -3,44 +3,43 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching +namespace Polly.Specs.Caching; + +public class SlidingTtlSpecs { - public class SlidingTtlSpecs + [Fact] + public void Should_throw_when_timespan_is_less_than_zero() { - [Fact] - public void Should_throw_when_timespan_is_less_than_zero() - { - Action configure = () => new SlidingTtl(TimeSpan.FromMilliseconds(-1)); + Action configure = () => new SlidingTtl(TimeSpan.FromMilliseconds(-1)); - configure.Should().Throw().And.ParamName.Should().Be("slidingTtl"); - } + configure.Should().Throw().And.ParamName.Should().Be("slidingTtl"); + } - [Fact] - public void Should_not_throw_when_timespan_is_zero() - { - Action configure = () => new SlidingTtl(TimeSpan.Zero); + [Fact] + public void Should_not_throw_when_timespan_is_zero() + { + Action configure = () => new SlidingTtl(TimeSpan.Zero); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_allow_timespan_max_value() - { - Action configure = () => new SlidingTtl(TimeSpan.MaxValue); + [Fact] + public void Should_allow_timespan_max_value() + { + Action configure = () => new SlidingTtl(TimeSpan.MaxValue); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_return_configured_timespan() - { - TimeSpan ttl = TimeSpan.FromSeconds(30); + [Fact] + public void Should_return_configured_timespan() + { + TimeSpan ttl = TimeSpan.FromSeconds(30); - SlidingTtl ttlStrategy = new SlidingTtl(ttl); + SlidingTtl ttlStrategy = new SlidingTtl(ttl); - Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().Be(ttl); - retrieved.SlidingExpiration.Should().BeTrue(); - } + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + retrieved.Timespan.Should().Be(ttl); + retrieved.SlidingExpiration.Should().BeTrue(); } } diff --git a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs index 7bef1149303..a0cf44c38c6 100644 --- a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs @@ -11,3101 +11,3100 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker -{ - [Collection(Constants.SystemClockDependentTestCollection)] - public class AdvancedCircuitBreakerAsyncSpecs : IDisposable - { - #region Configuration tests - - [Fact] - public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } - - [Fact] - public void Should_throw_if_failure_threshold_is_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } - - [Fact] - public void Should_throw_if_failure_threshold_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } - - [Fact] - public void Should_be_able_to_handle_a_failure_threshold_of_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().NotThrow(); - } - - [Fact] - public void Should_throw_if_failure_threshold_is_greater_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } - - [Fact] - public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync( - 0.5, - TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), - 4, - TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("samplingDuration"); - } - - [Fact] - public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); - - action.Should().NotThrow(); - } - - [Fact] - public void Should_throw_if_minimum_throughput_is_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } - - [Fact] - public void Should_throw_if_minimum_throughput_is_less_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } - - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } - - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); - - action.Should().NotThrow(); - } - - [Fact] - public void Should_initialise_to_closed_state() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region Circuit-breaker threshold-to-break tests - - #region Tests that are independent from health metrics implementation - - // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of three actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // Failure threshold exceeded, but throughput threshold not yet. - - // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public async Task Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .Or() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw unhandled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region With sample duration higher than 199 ms so that multiple windows are used - - // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - - [Fact] - public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - bool delegateExecutedWhenBroken = false; - var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice. - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. - - // Two actions at the start of the original timeslice. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Creates a new window right at the end of the original timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. - SystemClock.UtcNow = () => time.Add(samplingDuration); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); +namespace Polly.Specs.CircuitBreaker; - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public async Task Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Three of four actions in this test occur within the first timeslice. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // This failure opens the circuit, because it is the second failure of four calls - // equalling the failure threshold. The minimum threshold within the defined - // sampling duration is met, when using rolling windows. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public async Task Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Two of three actions in this test occur within the first timeslice. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // A third failure occurs just at the beginning of the new timeslice making - // the number of failures above the failure threshold. However, the throughput is - // below the minimum threshold as to open the circuit. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public async Task Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - var numberOfWindowsDefinedInCircuitBreaker = 10; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to the second window in the rolling metrics - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); - - // Three actions occur in the second window of the first timeslice - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - #endregion - - #region With sample duration at 199 ms so that only a single window is used - - // These tests on AdvancedCircuitBreakerAsync operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a 199ms period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first within the timeslice. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures; but only the first within the timeslice. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public async Task Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public async Task Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); +[Collection(Constants.SystemClockDependentTestCollection)] +public class AdvancedCircuitBreakerAsyncSpecs : IDisposable +{ + #region Configuration tests - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + [Fact] + public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); - // Three of four actions in this test occur within the first timeslice. - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_throw_if_failure_threshold_is_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); + [Fact] + public void Should_throw_if_failure_threshold_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - // This failure does not open the circuit, because a new duration should have - // started and with such low sampling duration, windows should not be used. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - #endregion + [Fact] + public void Should_be_able_to_handle_a_failure_threshold_of_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - #endregion + action.Should().NotThrow(); + } - #region Circuit-breaker open->half-open->open/closed tests + [Fact] + public void Should_throw_if_failure_threshold_is_greater_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - [Fact] - public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - var durationOfBreak = TimeSpan.FromSeconds(30); + [Fact] + public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync( + 0.5, + TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), + 4, + TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("samplingDuration"); + } - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + [Fact] + public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().NotThrow(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_throw_if_minimum_throughput_is_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public void Should_throw_if_minimum_throughput_is_less_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); - breaker.CircuitState.Should().Be(CircuitState.Open); + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - [Fact] - public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().NotThrow(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_initialise_to_closed_state() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - var anotherWindowDuration = samplingDuration.Seconds / 2d + 1; - SystemClock.UtcNow = () => time.AddSeconds(anotherWindowDuration); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Circuit-breaker threshold-to-break tests - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Tests that are independent from health metrics implementation - // Since the call that opened the circuit occurred in a later window, then the - // break duration must be simulated as from that call. - SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherWindowDuration); + // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of three actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // Failure threshold exceeded, but throughput threshold not yet. + + // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - [Fact] - public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .Or() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw unhandled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - var durationOfBreak = TimeSpan.FromSeconds(30); + #endregion - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + #region With sample duration higher than 199 ms so that multiple windows are used - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + bool delegateExecutedWhenBroken = false; + var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice. + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Open); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // first call after duration raises an exception, so circuit should open again - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - } + } - [Fact] - public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromSeconds(30); + } - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var samplingDuration = TimeSpan.FromSeconds(10); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + } - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + var samplingDuration = TimeSpan.FromSeconds(10); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + var samplingDuration = TimeSpan.FromSeconds(10); - // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Two actions at the start of the original timeslice. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // Creates a new window right at the end of the original timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - // exceptions raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. + SystemClock.UtcNow = () => time.Add(samplingDuration); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + } - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(async () => - { - await breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + } - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - })).Should().NotThrowAsync(); - }, TaskCreationOptions.LongRunning); + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + [Fact] + public async Task Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure opens the circuit, because it is the second failure of four calls + // equalling the failure threshold. The minimum threshold within the defined + // sampling duration is met, when using rolling windows. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - Task secondExecution = Task.Factory.StartNew(async () => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Two of three actions in this test occur within the first timeslice. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // A third failure occurs just at the beginning of the new timeslice making + // the number of failures above the failure threshold. However, the throughput is + // below the minimum threshold as to open the circuit. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - try - { - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + [Fact] + public async Task Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + var numberOfWindowsDefinedInCircuitBreaker = 10; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to the second window in the rolling metrics + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); + + // Three actions occur in the second window of the first timeslice + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + #endregion - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - } - } + #region With sample duration at 199 ms so that only a single window is used - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + // These tests on AdvancedCircuitBreakerAsync operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a 199ms period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + } - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + } - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(async () => - { - await breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + } - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; - })).Should().NotThrowAsync(); - }, TaskCreationOptions.LongRunning); + var samplingDuration = TimeSpan.FromMilliseconds(199); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - Task secondExecution = Task.Factory.StartNew(async () => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Four of four actions in this test throw handled failures; but only the first within the timeslice. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); - } - } + } - #endregion + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #region Isolate and reset tests + var samplingDuration = TimeSpan.FromMilliseconds(199); - [Fact] - public async Task Should_open_circuit_and_block_calls_if_manual_override_open() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - bool delegateExecutedWhenBroken = false; - await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + // Two of four actions in this test throw handled failures; but only the first within the timeslice. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - bool delegateExecutedWhenBroken = false; - await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().ThrowAsync(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public async Task Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().ThrowAsync(); + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - [Fact] - public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure does not open the circuit, because a new duration should have + // started and with such low sampling duration, windows should not be used. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - var durationOfBreak = TimeSpan.FromSeconds(30); + #endregion - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + #endregion - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #region Circuit-breaker open->half-open->open/closed tests - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - } + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region State-change delegate tests + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - var durationOfBreak = TimeSpan.FromSeconds(30); - Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - onResetCalled.Should().BeFalse(); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_automatically() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + var anotherWindowDuration = samplingDuration.Seconds / 2d + 1; + SystemClock.UtcNow = () => time.AddSeconds(anotherWindowDuration); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Since the call that opened the circuit occurred in a later window, then the + // break duration must be simulated as from that call. + SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherWindowDuration); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + var durationOfBreak = TimeSpan.FromSeconds(30); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - onBreakCalled.Should().BeTrue(); - } + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + // first call after duration raises an exception, so circuit should open again + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + } - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + [Fact] + public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromSeconds(30); - onBreakCalled.Should().Be(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - // call through circuit when already broken - should not retrigger onBreak - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - - using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) - { - Task longRunningExecution = Task.Factory.StartNew(async () => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - permitMainThreadToOpenCircuit.Set(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - })).Should().ThrowAsync(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // Break circuit in the normal manner: onBreak() should be called once. - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + + // exceptions raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. + + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; + + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(async () => + { + await breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } - } + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; - [Fact] - public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + })).Should().NotThrowAsync(); + }, TaskCreationOptions.LongRunning); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Task secondExecution = Task.Factory.StartNew(async () => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + try + { + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + await TaskHelper.EmptyTask; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } + } - onBreakCalled.Should().Be(1); + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; + + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(async () => + { + await breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; + })).Should().NotThrowAsync(); + }, TaskCreationOptions.LongRunning); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - [Fact] - public async Task Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + Task secondExecution = Task.Factory.StartNew(async () => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - onResetCalled.Should().BeFalse(); + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + await TaskHelper.EmptyTask; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); } + } - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + #endregion - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + #region Isolate and reset tests - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + [Fact] + public async Task Should_open_circuit_and_block_calls_if_manual_override_open() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + bool delegateExecutedWhenBroken = false; + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - onBreakCalled.Should().Be(1); + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - onHalfOpenCalled.Should().Be(1); // called as action was placed for execution - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); // called after action succeeded - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + bool delegateExecutedWhenBroken = false; + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + [Fact] + public async Task Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); + + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + [Fact] + public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + } - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + #endregion - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + #region State-change delegate tests - onBreakCalled.Should().Be(1); + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + var durationOfBreak = TimeSpan.FromSeconds(30); + Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - [Fact] - public async Task Should_call_onreset_when_manually_resetting_circuit() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + onResetCalled.Should().BeFalse(); + } - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_automatically() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().BeTrue(); + } - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().ThrowAsync(); + onBreakCalled.Should().BeFalse(); - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrowAsync(); - } + onBreakCalled.Should().BeTrue(); + } - #region Tests of supplied parameters to onBreak delegate + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + // call through circuit when already broken - should not retrigger onBreak + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public async Task Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; - - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + { + Task longRunningExecution = Task.Factory.StartNew(async () => + { + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + permitMainThreadToOpenCircuit.Set(); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Throw a further failure when rest of test has already broken the circuit. + breaker.CircuitState.Should().Be(CircuitState.Open); + throw new DivideByZeroException(); - passedException?.Should().BeOfType(); - } + })).Should().ThrowAsync(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - [Fact] - public async Task Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; - - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // Break circuit in the normal manner: onBreak() should be called once. breaker.CircuitState.Should().Be(CircuitState.Closed); - + onBreakCalled.Should().Be(0); await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - + .Should().ThrowAsync(); await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - transitionedState?.Should().Be(CircuitState.Closed); + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); } + } - [Fact] - public async Task Should_call_onbreak_with_a_state_of_half_open() - { - List transitionedStates = new List(); - - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); + + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + onResetCalled.Should().BeFalse(); - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + onHalfOpenCalled.Should().Be(1); // called as action was placed for execution + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); // called after action succeeded + } - // first call after duration raises an exception, so circuit should open again - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + [Fact] + public async Task Should_call_onreset_when_manually_resetting_circuit() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); + + breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); + + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); + + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + #region Tests of supplied parameters to onBreak delegate - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + [Fact] + public async Task Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; + + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + passedException?.Should().BeOfType(); + } - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public async Task Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; + + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + transitionedState?.Should().Be(CircuitState.Closed); + } - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + [Fact] + public async Task Should_call_onbreak_with_a_state_of_half_open() + { + List transitionedStates = new List(); - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - passedBreakTimespan.Should().Be(durationOfBreak); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromMinutes(1), - onBreak: onBreak, - onReset: onReset - ); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + breaker.CircuitState.Should().Be(CircuitState.Open); - #endregion + SystemClock.UtcNow = () => time.Add(durationOfBreak); - #region Tests that supplied context is passed to stage-change delegates + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public async Task Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; - - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // first call after duration raises an exception, so circuit should open again + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - await breaker.Awaiting(x => x.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + passedBreakTimespan.Should().Be(durationOfBreak); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromMinutes(1), + onBreak: onBreak, + onReset: onReset + ); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Tests that supplied context is passed to stage-change delegates - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; + + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + )).Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } + [Fact] + public async Task Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - // first call after duration should invoke onReset, with context - await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - contextData.Should().BeEmpty(); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; - - Action onBreak = - (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = - context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + // first call after duration should invoke onReset, with context + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #endregion + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); - #endregion + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region LastException property + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.LastException.Should().BeNull(); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.LastException.Should().BeOfType(); - } + contextData.Should().BeEmpty(); + } - [Fact] - public async Task Should_set_LastException_to_last_raised_exception_when_breaking() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; + + Action onBreak = + (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = + context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); + + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + #endregion - breaker.LastException.Should().BeOfType(); - } + #region LastException property - [Fact] - public async Task Should_set_LastException_to_null_on_circuit_reset() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + breaker.LastException.Should().BeNull(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.LastException.Should().BeOfType(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_set_LastException_to_last_raised_exception_when_breaking() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.LastException.Should().BeOfType(); + } - breaker.LastException.Should().BeOfType(); + [Fact] + public async Task Should_set_LastException_to_null_on_circuit_reset() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - breaker.Reset(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.LastException.Should().BeNull(); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #endregion + breaker.LastException.Should().BeOfType(); - #region Cancellation support + breaker.Reset(); - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.LastException.Should().BeNull(); + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + #endregion - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation support - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(1); + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(0); + } + + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + attemptsInvoked.Should().Be(1); + } - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - // Circuit is now broken. + [Fact] + public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - cancellationTokenSource.Cancel(); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + // Circuit is now broken. - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex2 = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex2.And.CancellationToken.Should().Be(cancellationToken); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + cancellationTokenSource.Cancel(); - [Fact] - public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() + Scenario scenario = new Scenario { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var ex2 = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex2.And.CancellationToken.Should().Be(cancellationToken); - CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); - CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; + attemptsInvoked.Should().Be(0); + } - CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + [Fact] + public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. - implicitlyCapturedActionCancellationTokenSource.Cancel(); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - int attemptsInvoked = 0; + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => - { - attemptsInvoked++; - await TaskHelper.EmptyTask; - implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); - }, policyCancellationToken)) - .Should().ThrowAsync(); + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; - [Fact] - public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + attemptsInvoked++; + await TaskHelper.EmptyTask; + implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); + }, policyCancellationToken)) + .Should().ThrowAsync(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - await breaker.Awaiting(action) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public async Task Should_honour_and_report_cancellation_during_func_execution() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + await breaker.Awaiting(action) + .Should().NotThrowAsync(); - bool? result = null; + result.Should().BeTrue(); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(1); + } - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - var ex = await breaker.Awaiting(action) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + [Fact] + public async Task Should_honour_and_report_cancellation_during_func_execution() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - result.Should().Be(null); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - #endregion + bool? result = null; - public void Dispose() + Scenario scenario = new Scenario { - SystemClock.Reset(); - } + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + var ex = await breaker.Awaiting(action) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + + result.Should().Be(null); + + attemptsInvoked.Should().Be(1); + } + + #endregion + + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs index a565bd02f5a..3504018b616 100644 --- a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs @@ -11,3079 +11,3078 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensions.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker -{ - - [Collection(Constants.SystemClockDependentTestCollection)] - public class AdvancedCircuitBreakerSpecs : IDisposable - { - #region Configuration tests - - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_if_failure_threshold_is_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } - - [Fact] - public void Should_throw_if_failure_threshold_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } - - [Fact] - public void Should_be_able_to_handle_a_failure_threshold_of_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().NotThrow(); - } - - [Fact] - public void Should_throw_if_failure_threshold_is_greater_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } - - [Fact] - public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker( - 0.5, - TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), - 4, - TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("samplingDuration"); - } - - [Fact] - public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); - - action.Should().NotThrow(); - } - - [Fact] - public void Should_throw_if_minimum_throughput_is_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } - - [Fact] - public void Should_throw_if_minimum_throughput_is_less_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } - - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } - - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); - - action.Should().NotThrow(); - } - - [Fact] - public void Should_initialise_to_closed_state() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region Circuit-breaker threshold-to-break tests - - #region Tests that are independent from health metrics implementation - - // Tests on the AdvancedCircuitBreaker operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of three actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // Failure threshold exceeded, but throughput threshold not yet. - - // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .Or() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw unhandled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region With sample duration higher than 199 ms so that multiple windows are used - - // Tests on the AdvancedCircuitBreaker operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice. - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. - - // Two actions at the start of the original timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Creates a new window right at the end of the original timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. - SystemClock.UtcNow = () => time.Add(samplingDuration); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } - - [Fact] - public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Three of four actions in this test occur within the first timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // This failure opens the circuit, because it is the second failure of four calls - // equalling the failure threshold. The minimum threshold within the defined - // sampling duration is met, when using rolling windows. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Two of three actions in this test occur within the first timeslice. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // A third failure occurs just at the beginning of the new timeslice making - // the number of failures above the failure threshold. However, the throughput is - // below the minimum threshold as to open the circuit. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - var numberOfWindowsDefinedInCircuitBreaker = 10; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to the second window in the rolling metrics - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); - - // Three actions occur in the second window of the first timeslice - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - #endregion - - #region With sample duration at 199 ms so that only a single window is used - - // These tests on AdvancedCircuitBreaker operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a 199ms period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); +namespace Polly.Specs.CircuitBreaker; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - } - - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); +[Collection(Constants.SystemClockDependentTestCollection)] +public class AdvancedCircuitBreakerSpecs : IDisposable +{ + #region Configuration tests - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - } + [Fact] + public void Should_throw_if_failure_threshold_is_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - var samplingDuration = TimeSpan.FromMilliseconds(199); + [Fact] + public void Should_throw_if_failure_threshold_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_be_able_to_handle_a_failure_threshold_of_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().NotThrow(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_throw_if_failure_threshold_is_greater_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker( + 0.5, + TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), + 4, + TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("samplingDuration"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); - } + action.Should().NotThrow(); + } - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_throw_if_minimum_throughput_is_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + [Fact] + public void Should_throw_if_minimum_throughput_is_less_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); - [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + action.Should().NotThrow(); + } - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + [Fact] + public void Should_initialise_to_closed_state() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - // Three of four actions in this test occur within the first timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #region Circuit-breaker threshold-to-break tests - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); + #region Tests that are independent from health metrics implementation - // This failure does not open the circuit, because a new duration should have - // started and with such low sampling duration, windows should not be used. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + // Tests on the AdvancedCircuitBreaker operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - #endregion + [Fact] + public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of three actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // Failure threshold exceeded, but throughput threshold not yet. + + // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - #endregion + [Fact] + public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .Or() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw unhandled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - #region Circuit-breaker open->half-open->open/closed tests + #endregion - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + #region With sample duration higher than 199 ms so that multiple windows are used - var durationOfBreak = TimeSpan.FromSeconds(30); + // Tests on the AdvancedCircuitBreaker operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice. + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Open); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + } - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - var samplingDuration = TimeSpan.FromSeconds(10); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - var anotherwindowDuration = samplingDuration.Seconds / 2d + 1; - SystemClock.UtcNow = () => time.AddSeconds(anotherwindowDuration); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.CircuitState.Should().Be(CircuitState.Open); + var samplingDuration = TimeSpan.FromSeconds(10); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Open); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - // Since the call that opened the circuit occurred in a later window, then the - // break duration must be simulated as from that call. - SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherwindowDuration); + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var samplingDuration = TimeSpan.FromSeconds(10); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // first call after duration raises an exception, so circuit should open again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); - [Fact] - public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + } - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var samplingDuration = TimeSpan.FromSeconds(10); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // Two actions at the start of the original timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // Creates a new window right at the end of the original timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => {}); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. + SystemClock.UtcNow = () => time.Add(samplingDuration); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + } - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + } - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + [Fact] + public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure opens the circuit, because it is the second failure of four calls + // equalling the failure threshold. The minimum threshold within the defined + // sampling duration is met, when using rolling windows. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Two of three actions in this test occur within the first timeslice. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // A third failure occurs just at the beginning of the new timeslice making + // the number of failures above the failure threshold. However, the throughput is + // below the minimum threshold as to open the circuit. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + [Fact] + public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + var numberOfWindowsDefinedInCircuitBreaker = 10; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to the second window in the rolling metrics + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); + + // Three actions occur in the second window of the first timeslice + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + #endregion - var durationOfBreak = TimeSpan.FromSeconds(30); + #region With sample duration at 199 ms so that only a single window is used - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + // These tests on AdvancedCircuitBreaker operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a 199ms period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + } - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + var samplingDuration = TimeSpan.FromMilliseconds(199); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - var durationOfBreak = TimeSpan.FromSeconds(30); + // Four of four actions in this test throw handled failures; but only the first within the timeslice. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // exceptions raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + } - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => - { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var samplingDuration = TimeSpan.FromMilliseconds(199); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + // Two of four actions in this test throw handled failures; but only the first within the timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Task secondExecution = Task.Factory.StartNew(() => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); - try - { - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + } - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - } - } + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + } - var durationOfBreak = TimeSpan.FromSeconds(30); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure does not open the circuit, because a new duration should have + // started and with such low sampling duration, windows should not be used. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + #endregion - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; - - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => - { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + #endregion - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + #region Circuit-breaker open->half-open->open/closed tests - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + var durationOfBreak = TimeSpan.FromSeconds(30); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - Task secondExecution = Task.Factory.StartNew(() => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); - } - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Isolate and reset tests + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + var samplingDuration = TimeSpan.FromSeconds(10); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + var anotherwindowDuration = samplingDuration.Seconds / 2d + 1; + SystemClock.UtcNow = () => time.AddSeconds(anotherwindowDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Since the call that opened the circuit occurred in a later window, then the + // break duration must be simulated as from that call. + SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherwindowDuration); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromSeconds(30); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromSeconds(30); + // first call after duration raises an exception, so circuit should open again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + [Fact] + public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - #region State-change delegate tests + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var durationOfBreak = TimeSpan.FromSeconds(30); - Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onResetCalled.Should().BeFalse(); - } + // first call after duration is successful, so circuit should reset + breaker.Execute(() => {}); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; - var durationOfBreak = TimeSpan.FromSeconds(30); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onBreakCalled.Should().BeFalse(); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - breaker.Isolate(); + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - onBreakCalled.Should().BeTrue(); - } + var durationOfBreak = TimeSpan.FromSeconds(30); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // call through circuit when already broken - should not retrigger onBreak - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } + + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + // exceptions raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. + + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; + + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(() => { - Task longRunningExecution = Task.Factory.StartNew(() => + breaker.Invoking(x => x.Execute(() => { - breaker.CircuitState.Should().Be(CircuitState.Closed); + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - breaker.Invoking(x => x.Execute(() => - { - permitMainThreadToOpenCircuit.Set(); - - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - - // Break circuit in the normal manner: onBreak() should be called once. - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + Task secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + try + { + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); } + } - [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; + + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - onBreakCalled.Should().Be(1); + Task secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); } + } - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; - - var durationOfBreak = TimeSpan.FromSeconds(30); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - - onResetCalled.Should().BeFalse(); - - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + #endregion - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + #region Isolate and reset tests - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + var durationOfBreak = TimeSpan.FromSeconds(30); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - onBreakCalled.Should().Be(1); + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); + + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - onHalfOpenCalled.Should().Be(1); // called as action was placed for execution - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); // called after action succeeded - } + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + #endregion - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + #region State-change delegate tests - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + var durationOfBreak = TimeSpan.FromSeconds(30); + Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + onResetCalled.Should().BeFalse(); + } - onBreakCalled.Should().Be(1); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().BeTrue(); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + var durationOfBreak = TimeSpan.FromSeconds(30); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().BeFalse(); - var durationOfBreak = TimeSpan.FromSeconds(30); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + breaker.Isolate(); - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + onBreakCalled.Should().BeTrue(); + } + + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + // call through circuit when already broken - should not retrigger onBreak + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + { + Task longRunningExecution = Task.Factory.StartNew(() => + { + breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + breaker.Invoking(x => x.Execute(() => + { + permitMainThreadToOpenCircuit.Set(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - #region Tests of supplied parameters to onBreak delegate + // Throw a further failure when rest of test has already broken the circuit. + breaker.CircuitState.Should().Be(CircuitState.Open); + throw new DivideByZeroException(); - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; - - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); + })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // Break circuit in the normal manner: onBreak() should be called once. breaker.CircuitState.Should().Be(CircuitState.Closed); - + onBreakCalled.Should().Be(0); breaker.Invoking(x => x.RaiseException()) .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); + + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - passedException?.Should().BeOfType(); + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); } + } - [Fact] - public void Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; - - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); + [Fact] + public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); + + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - transitionedState?.Should().Be(CircuitState.Closed); - } + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() - { - List transitionedStates = new List(); - - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + onHalfOpenCalled.Should().Be(1); // called as action was placed for execution + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); // called after action succeeded + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromSeconds(30); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); - // first call after duration raises an exception, so circuit should open again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + #region Tests of supplied parameters to onBreak delegate - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; + + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + passedException?.Should().BeOfType(); + } - TimeSpan durationOfBreak = TimeSpan.FromSeconds(30); + [Fact] + public void Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; + + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + transitionedState?.Should().Be(CircuitState.Closed); + } - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + [Fact] + public void Should_call_onbreak_with_a_state_of_half_open() + { + List transitionedStates = new List(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); - passedBreakTimespan.Should().Be(durationOfBreak); - } + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Tests that supplied context is passed to stage-change delegates + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; - - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // first call after duration raises an exception, so circuit should open again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - breaker.Invoking(x => x.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - [Fact] - public void Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + TimeSpan durationOfBreak = TimeSpan.FromSeconds(30); - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + passedBreakTimespan.Should().Be(durationOfBreak); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Tests that supplied context is passed to stage-change delegates - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; + + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + )).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } + [Fact] + public void Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - // first call after duration should invoke onReset, with context - breaker.Execute(_ => { }, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - contextData.Should().BeEmpty(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; - - Action onBreak = - (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = - context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException(new { key = "original_value" }.AsDictionary())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + // first call after duration should invoke onReset, with context + breaker.Execute(_ => { }, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - // first call after duration is successful, so circuit should reset - breaker.Execute(_ => { }, new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - #endregion + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #endregion + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); - #region LastException property + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.LastException.Should().BeNull(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.LastException.Should().BeOfType(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + contextData.Should().BeEmpty(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; + + Action onBreak = + (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = + context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException(new { key = "original_value" }.AsDictionary())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + + // first call after duration is successful, so circuit should reset + breaker.Execute(_ => { }, new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + #endregion - breaker.LastException.Should().BeOfType(); - } + #endregion - [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + #region LastException property - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + breaker.LastException.Should().BeNull(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.LastException.Should().BeOfType(); + } - breaker.LastException.Should().BeOfType(); + [Fact] + public void Should_set_LastException_to_last_raised_exception_when_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.LastException.Should().BeOfType(); + } - breaker.Reset(); + [Fact] + public void Should_set_LastException_to_null_on_circuit_reset() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - breaker.LastException.Should().BeNull(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Cancellation support + breaker.LastException.Should().BeOfType(); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.Reset(); + + breaker.LastException.Should().BeNull(); + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + #endregion - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation support - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(1); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(0); + } + + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + attemptsInvoked.Should().Be(1); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - // Circuit is now broken. + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - cancellationTokenSource.Cancel(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + // Circuit is now broken. - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + cancellationTokenSource.Cancel(); - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + Scenario scenario = new Scenario { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(0); + } - CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); - CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. - CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - implicitlyCapturedActionCancellationTokenSource.Cancel(); + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - int attemptsInvoked = 0; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - breaker.Invoking(x => x.Execute(_ => - { - attemptsInvoked++; - implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); - }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + breaker.Invoking(x => x.Execute(_ => { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + attemptsInvoked++; + implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); + }, policyCancellationToken)) + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs index 3116cf86673..4b1fe9dec51 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs @@ -11,1688 +11,1687 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker -{ - [Collection(Constants.SystemClockDependentTestCollection)] - public class CircuitBreakerAsyncSpecs : IDisposable - { - #region Configuration tests - - [Fact] - public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.MaxValue); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } - - [Fact] - public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .Handle() - .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("exceptionsAllowedBeforeBreaking"); - } - - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } +namespace Polly.Specs.CircuitBreaker; - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - action.Should().NotThrow(); - } +[Collection(Constants.SystemClockDependentTestCollection)] +public class CircuitBreakerAsyncSpecs : IDisposable +{ + #region Configuration tests - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.MaxValue); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .Handle() + .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); - #endregion + action.Should().Throw() + .And.ParamName.Should() + .Be("exceptionsAllowedBeforeBreaking"); + } - #region Circuit-breaker threshold-to-break tests + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); - [Fact] - public async Task Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - await breaker.Awaiting(b => b.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - [Fact] - public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Circuit-breaker threshold-to-break tests - bool delegateExecutedWhenBroken = false; - var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); + [Fact] + public async Task Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() - { - var breaker = Policy - .Handle() - .Or() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + await breaker.Awaiting(b => b.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + bool delegateExecutedWhenBroken = false; + var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); - // 2 exception raised, circuit is now open - bool delegateExecutedWhenBroken = false; - var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public async Task Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public async Task Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() + { + var breaker = Policy + .Handle() + .Or() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + bool delegateExecutedWhenBroken = false; + var ex = await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() - { - var breaker = Policy - .Handle() - .Or() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() + { + var breaker = Policy + .Handle() + .Or() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + #region Circuit-breaker open->half-open->open/closed tests - #endregion + [Fact] + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #region Circuit-breaker open->half-open->open/closed tests + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + // circuit has been reset so should once again allow 2 exceptions to be raised before breaking + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // circuit has been reset so should once again allow 2 exceptions to be raised before breaking - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(async () => + { + await breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + })).Should().NotThrowAsync(); + }, TaskCreationOptions.LongRunning); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + Task secondExecution = Task.Factory.StartNew(async () => { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(async () => + try { - await breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.ExecuteAsync(async () => { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); - - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. await TaskHelper.EmptyTask; - firstExecutionActive = false; - - })).Should().NotThrowAsync(); - }, TaskCreationOptions.LongRunning); - - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - - Task secondExecution = Task.Factory.StartNew(async () => + }); + } + catch (BrokenCircuitException) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - try - { - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + // Release first execution soon as second overlapping execution is done gathering data. permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - } + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); } + } - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(async () => + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(async () => + { + await breaker.Awaiting(x => x.ExecuteAsync(async () => { - await breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; - })).Should().NotThrowAsync(); - }, TaskCreationOptions.LongRunning); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; + })).Should().NotThrowAsync(); + }, TaskCreationOptions.LongRunning); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - Task secondExecution = Task.Factory.StartNew(async () => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + Task secondExecution = Task.Factory.StartNew(async () => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) + await breaker.ExecuteAsync(async () => { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + await TaskHelper.EmptyTask; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + // Release first execution soon as second overlapping execution is done gathering data. permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); - } + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); } + } - #endregion + #endregion - #region Isolate and reset tests + #region Isolate and reset tests - [Fact] - public async Task Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - bool delegateExecutedWhenBroken = false; - await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + bool delegateExecutedWhenBroken = false; + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - bool delegateExecutedWhenBroken = false; - await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().ThrowAsync(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + bool delegateExecutedWhenBroken = false; + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().ThrowAsync(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public async Task Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().ThrowAsync(); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); - } + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); + } - [Fact] - public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); - } + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); + } - #endregion + #endregion - #region State-change delegate tests + #region State-change delegate tests - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onResetCalled.Should().BeFalse(); - } + onResetCalled.Should().BeFalse(); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_automatically() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_automatically() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - breaker.Isolate(); + breaker.Isolate(); - onBreakCalled.Should().BeTrue(); - } + onBreakCalled.Should().BeTrue(); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - // call through circuit when already broken - should not retrigger onBreak - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // call through circuit when already broken - should not retrigger onBreak + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + Task longRunningExecution = Task.Factory.StartNew(async () => { - Task longRunningExecution = Task.Factory.StartNew(async () => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; - - permitMainThreadToOpenCircuit.Set(); - - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); - - })).Should().ThrowAsync(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); - - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - - // Break circuit in the normal manner: onBreak() should be called once. breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); - - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } - } + await breaker.Awaiting(x => x.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; - [Fact] - public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + permitMainThreadToOpenCircuit.Set(); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - var durationOfBreak = TimeSpan.FromMinutes(1); + // Throw a further failure when rest of test has already broken the circuit. + breaker.CircuitState.Should().Be(CircuitState.Open); + throw new DivideByZeroException(); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + })).Should().ThrowAsync(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - onBreakCalled.Should().Be(0); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) .Should().ThrowAsync(); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); } + } - [Fact] - public async Task Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + [Fact] + public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - onResetCalled.Should().BeFalse(); + var durationOfBreak = TimeSpan.FromMinutes(1); - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + onBreakCalled.Should().Be(0); - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + onBreakCalled.Should().Be(0); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - onBreakCalled.Should().Be(0); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - onBreakCalled.Should().Be(0); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - onBreakCalled.Should().Be(1); + [Fact] + public async Task Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + onResetCalled.Should().BeFalse(); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + onBreakCalled.Should().Be(0); + + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + [Fact] + public async Task Should_call_onreset_when_manually_resetting_circuit() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - onBreakCalled.Should().Be(0); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - onBreakCalled.Should().Be(0); + var durationOfBreak = TimeSpan.FromMinutes(1); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - onBreakCalled.Should().Be(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().ThrowAsync(); - [Fact] - public async Task Should_call_onreset_when_manually_resetting_circuit() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + #region Tests of supplied parameters to onBreak delegate - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + [Fact] + public async Task Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Isolated); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().ThrowAsync(); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrowAsync(); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Tests of supplied parameters to onBreak delegate + passedException?.Should().BeOfType(); + } - [Fact] - public async Task Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; + [Fact] + public async Task Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - passedException?.Should().BeOfType(); - } + transitionedState?.Should().Be(CircuitState.Closed); + } - [Fact] - public async Task Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; + [Fact] + public async Task Should_call_onbreak_with_a_state_of_half_open() + { + List transitionedStates = new List(); - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - transitionedState?.Should().Be(CircuitState.Closed); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_call_onbreak_with_a_state_of_half_open() - { - List transitionedStates = new List(); - - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public async Task Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() + { + Exception passedException = null; - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() - { - Exception passedException = null; + var breaker = Policy + .HandleInner() + .Or() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - var breaker = Policy - .HandleInner() - .Or() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) + .Should().ThrowAsync(); + ex.Which.Should().BeSameAs(toRaiseAsInner); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + passedException?.Should().BeSameAs(toRaiseAsInner); + } - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) - .Should().ThrowAsync(); - ex.Which.Should().BeSameAs(toRaiseAsInner); + [Fact] + public async Task Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - passedException?.Should().BeSameAs(toRaiseAsInner); - } + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + passedBreakTimespan.Should().Be(durationOfBreak); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - passedBreakTimespan.Should().Be(durationOfBreak); - } + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + #region Tests that supplied context is passed to stage-change delegates - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + [Fact] + public async Task Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - #endregion + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - #region Tests that supplied context is passed to stage-change delegates + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public async Task Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + await breaker.Awaiting(x => x.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + )).Should().ThrowAsync(); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().ThrowAsync(); + [Fact] + public async Task Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + // first call after duration should invoke onReset, with context + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - // first call after duration should invoke onReset, with context - await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + contextData.Should().BeEmpty(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - contextData.Should().BeEmpty(); - } + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // 2 exception raised, circuit is now open + await breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - // 2 exception raised, circuit is now open - await breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #endregion - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + #endregion - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + #region LastException property - #endregion + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - #endregion + breaker.LastException.Should().BeNull(); + } - #region LastException property + [Fact] + public async Task Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.LastException.Should().BeNull(); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.LastException.Should().BeOfType(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() + { + var breaker = Policy + .HandleInner() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - breaker.LastException.Should().BeOfType(); - } + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) + .Should().ThrowAsync(); + ex.Which.Should().BeSameAs(toRaiseAsInner); - [Fact] - public async Task Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() - { - var breaker = Policy - .HandleInner() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + breaker.LastException.Should().BeSameAs(toRaiseAsInner); + } - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) - .Should().ThrowAsync(); - ex.Which.Should().BeSameAs(toRaiseAsInner); + [Fact] + public async Task Should_set_LastException_to_last_raised_exception_when_breaking() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.LastException.Should().BeSameAs(toRaiseAsInner); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - [Fact] - public async Task Should_set_LastException_to_last_raised_exception_when_breaking() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + breaker.LastException.Should().BeOfType(); + } - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_set_LastException_to_null_on_circuit_reset() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - breaker.LastException.Should().BeOfType(); - } + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - [Fact] - public async Task Should_set_LastException_to_null_on_circuit_reset() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + breaker.LastException.Should().BeOfType(); - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastException.Should().BeNull(); + } - breaker.LastException.Should().BeOfType(); + #endregion - breaker.Reset(); + #region Cancellation support - breaker.LastException.Should().BeNull(); - } + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - #endregion + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - #region Cancellation support + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); - await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); + await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - attemptsInvoked.Should().Be(1); - } + var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + ex.WithInnerException(); + // Circuit is now broken. - [Fact] - public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var ex = await breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - ex.WithInnerException(); - // Circuit is now broken. + cancellationTokenSource.Cancel(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex2 = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex2.And.CancellationToken.Should().Be(cancellationToken); - cancellationTokenSource.Cancel(); + attemptsInvoked.Should().Be(0); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. - var ex2 = await breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex2.And.CancellationToken.Should().Be(cancellationToken); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - attemptsInvoked.Should().Be(0); - } + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - [Fact] - public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); - CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; + int attemptsInvoked = 0; - CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => + { + attemptsInvoked++; + await TaskHelper.EmptyTask; + implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); + }, policyCancellationToken)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + + attemptsInvoked.Should().Be(1); + } - implicitlyCapturedActionCancellationTokenSource.Cancel(); + [Fact] + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - int attemptsInvoked = 0; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => - { - attemptsInvoked++; - await TaskHelper.EmptyTask; - implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); - }, policyCancellationToken)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + bool? result = null; + + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + await breaker.Awaiting(action) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + result.Should().BeTrue(); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_honour_and_report_cancellation_during_func_execution() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - await breaker.Awaiting(action) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public async Task Should_honour_and_report_cancellation_during_func_execution() + Scenario scenario = new Scenario { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + var ex = await breaker.Awaiting(action) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - bool? result = null; - - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + result.Should().Be(null); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - var ex = await breaker.Awaiting(action) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - result.Should().Be(null); - - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs index 2b7a14c711f..b6039630023 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs @@ -9,1703 +9,1702 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.CircuitBreaker -{ - [Collection(Constants.SystemClockDependentTestCollection)] - public class CircuitBreakerSpecs : IDisposable - { - #region Configuration tests - - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.MaxValue); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .Handle() - .CircuitBreaker(0, TimeSpan.FromSeconds(10)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("exceptionsAllowedBeforeBreaking"); - } - - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } +namespace Polly.Specs.CircuitBreaker; - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - action.Should().NotThrow(); - } - - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region Circuit-breaker threshold-to-break tests - - [Fact] - public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.Execute(() =>{})).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); +[Collection(Constants.SystemClockDependentTestCollection)] +public class CircuitBreakerSpecs : IDisposable +{ + #region Configuration tests - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.MaxValue); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - } + [Fact] + public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .Handle() + .CircuitBreaker(0, TimeSpan.FromSeconds(10)); - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + action.Should().Throw() + .And.ParamName.Should() + .Be("exceptionsAllowedBeforeBreaking"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - // 2 exception raised, circuit is now open - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + #endregion - [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + #region Circuit-breaker threshold-to-break tests - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Invoking(b => b.Execute(() =>{})).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - #region Circuit-breaker open->half-open->open/closed tests + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #endregion - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + #region Circuit-breaker open->half-open->open/closed tests - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => {}); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // circuit has been reset so should once again allow 2 exceptions to be raised before breaking - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => {}); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // circuit has been reset so should once again allow 2 exceptions to be raised before breaking + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should reject a second execution in the same time window. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should reject a second execution in the same time window. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. + + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; + + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + Task secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + try { - breaker.Invoking(x => x.Execute(() => + breaker.Execute(() => { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); - - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; - - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); - - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - - Task secondExecution = Task.Factory.StartNew(() => + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - try - { - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } - - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + // Release first execution soon as second overlapping execution is done gathering data. permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - - } + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } + } - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - Task secondExecution = Task.Factory.StartNew(() => + Task secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + try { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) + breaker.Execute(() => { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } - - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + // Release first execution soon as second overlapping execution is done gathering data. permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] {firstExecution, secondExecution}, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); - } + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] {firstExecution, secondExecution}, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); } + } - #endregion - - #region Isolate and reset tests - - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); - } - - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + #endregion - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + #region Isolate and reset tests - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #endregion + var durationOfBreak = TimeSpan.FromMinutes(1); - #region State-change delegate tests + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - onResetCalled.Should().BeFalse(); - } + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + #endregion - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + #region State-change delegate tests - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + onResetCalled.Should().BeFalse(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - bool onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Isolate(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + bool onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + onBreakCalled.Should().BeTrue(); + } - // call through circuit when already broken - should not retrigger onBreak - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - int onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) - { - Task longRunningExecution = Task.Factory.StartNew(() => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.Execute(() => - { - permitMainThreadToOpenCircuit.Set(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); + // call through circuit when already broken - should not retrigger onBreak + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - // Break circuit in the normal manner: onBreak() should be called once. + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + int onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + { + Task longRunningExecution = Task.Factory.StartNew(() => + { breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + breaker.Invoking(x => x.Execute(() => + { + permitMainThreadToOpenCircuit.Set(); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - // onBreak() should still only have been called once. + // Throw a further failure when rest of test has already broken the circuit. breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } - } - - [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + throw new DivideByZeroException(); - var durationOfBreak = TimeSpan.FromMinutes(1); + })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(0); - breaker.Invoking(x => x.RaiseException()) .Should().Throw(); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); } + } - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; - - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - - onResetCalled.Should().BeFalse(); + [Fact] + public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } - - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + var durationOfBreak = TimeSpan.FromMinutes(1); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + onBreakCalled.Should().Be(0); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(0); - onBreakCalled.Should().Be(0); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(1); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(0); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onResetCalled.Should().BeFalse(); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - onBreakCalled.Should().Be(0); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(1); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); + #region Tests of supplied parameters to onBreak delegate - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - #region Tests of supplied parameters to onBreak delegate + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + passedException?.Should().BeOfType(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; - passedException?.Should().BeOfType(); - } + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - [Fact] - public void Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + transitionedState?.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_with_a_state_of_half_open() + { + List transitionedStates = new List(); - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + Action onHalfOpen = () => { }; - transitionedState?.Should().Be(CircuitState.Closed); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() - { - List transitionedStates = new List(); + var durationOfBreak = TimeSpan.FromMinutes(1); - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - Action onHalfOpen = () => { }; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() + { + Exception passedException = null; - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() - { - Exception passedException = null; + CircuitBreakerPolicy breaker = Policy + .HandleInner() + .Or() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .HandleInner() - .Or() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.Invoking(x => x.RaiseException(withInner)) + .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + passedException?.Should().BeSameAs(toRaiseAsInner); + } - breaker.Invoking(x => x.RaiseException(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - passedException?.Should().BeSameAs(toRaiseAsInner); - } + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + passedBreakTimespan.Should().Be(durationOfBreak); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Open); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - passedBreakTimespan.Should().Be(durationOfBreak); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - var durationOfBreak = TimeSpan.FromMinutes(1); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + #region Tests that supplied context is passed to stage-change delegates - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - #endregion + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - #region Tests that supplied context is passed to stage-change delegates + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + breaker.Invoking(x => x.RaiseException( + new {key1 = "value1", key2 = "value2"}.AsDictionary() + )).Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - breaker.Invoking(x => x.RaiseException( - new {key1 = "value1", key2 = "value2"}.AsDictionary() - )).Should().Throw(); + [Fact] + public void Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + // first call after duration should invoke onReset, with context + breaker.Execute(_ => { }, new {key1 = "value1", key2 = "value2"}.AsDictionary()); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + IDictionary contextData = new {key1 = "value1", key2 = "value2"}.AsDictionary(); - // first call after duration should invoke onReset, with context - breaker.Execute(_ => { }, new {key1 = "value1", key2 = "value2"}.AsDictionary()); + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - IDictionary contextData = new {key1 = "value1", key2 = "value2"}.AsDictionary(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + contextData.Should().BeEmpty(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - contextData.Should().BeEmpty(); - } + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException(new {key = "original_value"}.AsDictionary())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException(new {key = "original_value"}.AsDictionary())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + // first call after duration is successful, so circuit should reset + breaker.Execute(_ => { }, new {key = "new_value"}.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #endregion - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + #endregion - // first call after duration is successful, so circuit should reset - breaker.Execute(_ => { }, new {key = "new_value"}.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + #region LastException property - #endregion + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - #endregion + breaker.LastException.Should().BeNull(); + } - #region LastException property + [Fact] + public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.LastException.Should().BeNull(); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.LastException.Should().BeOfType(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() + { + CircuitBreakerPolicy breaker = Policy + .HandleInner() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.LastException.Should().BeOfType(); - } + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - [Fact] - public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() - { - CircuitBreakerPolicy breaker = Policy - .HandleInner() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(x => x.RaiseException(withInner)) + .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + breaker.LastException.Should().BeSameAs(toRaiseAsInner); + } - breaker.Invoking(x => x.RaiseException(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + [Fact] + public void Should_set_LastException_to_last_raised_exception_when_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.LastException.Should().BeSameAs(toRaiseAsInner); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.LastException.Should().BeOfType(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_set_LastException_to_null_on_circuit_reset() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.LastException.Should().BeOfType(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.LastException.Should().BeOfType(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastException.Should().BeNull(); + } - breaker.LastException.Should().BeOfType(); + #endregion - breaker.Reset(); + #region ExecuteAndCapture with HandleInner - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_set_PolicyResult_on_handling_inner_exception() + { + CircuitBreakerPolicy breaker = Policy + .HandleInner() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - #endregion - #region ExecuteAndCapture with HandleInner + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - [Fact] - public void Should_set_PolicyResult_on_handling_inner_exception() - { - CircuitBreakerPolicy breaker = Policy - .HandleInner() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + PolicyResult policyResult = breaker.ExecuteAndCapture(() => throw withInner); + policyResult.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + policyResult.FinalException.Should().BeSameAs(toRaiseAsInner); + } - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + #endregion - PolicyResult policyResult = breaker.ExecuteAndCapture(() => throw withInner); + #region Cancellation support - policyResult.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - policyResult.FinalException.Should().BeSameAs(toRaiseAsInner); - } + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - #endregion + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - #region Cancellation support + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.FromMinutes(1)); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - attemptsInvoked.Should().Be(1); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + // Circuit is now broken. - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.FromMinutes(1)); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - // Circuit is now broken. + cancellationTokenSource.Cancel(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - cancellationTokenSource.Cancel(); + attemptsInvoked.Should().Be(0); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - attemptsInvoked.Should().Be(0); - } + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); - CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; + int attemptsInvoked = 0; - CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + breaker.Invoking(x => x.Execute(_ => + { + attemptsInvoked++; + implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); + }, policyCancellationToken)) + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - implicitlyCapturedActionCancellationTokenSource.Cancel(); + attemptsInvoked.Should().Be(1); + } - int attemptsInvoked = 0; + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.Execute(_ => - { - attemptsInvoked++; - implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); - }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + bool? result = null; + + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + result.Should().BeTrue(); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - bool? result = null; - - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + result.Should().Be(null); - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - - result.Should().Be(null); - - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs index 4e897abbf5d..6922ce5464d 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs @@ -11,1595 +11,1594 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker -{ - [Collection(Constants.SystemClockDependentTestCollection)] - public class CircuitBreakerTResultAsyncSpecs : IDisposable - { - #region Configuration tests - - [Fact] - public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.MaxValue); +namespace Polly.Specs.CircuitBreaker; - var result = await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault); - result.Should().Be(ResultPrimitive.Fault); - } - - [Fact] - public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); +[Collection(Constants.SystemClockDependentTestCollection)] +public class CircuitBreakerTResultAsyncSpecs : IDisposable +{ + #region Configuration tests - action.Should().Throw() - .And.ParamName.Should() - .Be("handledEventsAllowedBeforeBreaking"); - } + [Fact] + public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.MaxValue); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); + var result = await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault); + result.Should().Be(ResultPrimitive.Fault); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + [Fact] + public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.Zero); - action.Should().NotThrow(); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("handledEventsAllowedBeforeBreaking"); + } - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - #endregion + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - #region Circuit-breaker threshold-to-break tests + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - [Fact] - public async Task Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #region Circuit-breaker threshold-to-break tests - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public async Task Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.Fault); + [Fact] + public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.Fault); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Good))) - .Should().ThrowAsync>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Good))) + .Should().ThrowAsync>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public async Task Should_not_open_circuit_if_result_returned_is_not_the_handled_result() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public async Task Should_not_open_circuit_if_result_returned_is_not_the_handled_result() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public async Task Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultYetAgain) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public async Task Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultYetAgain) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + #region Circuit-breaker open->half-open->open/closed tests - [Fact] - public async Task Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Circuit-breaker open->half-open->open/closed tests + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + } - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + [Fact] + public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - } + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + // first call after duration returns a fault, so circuit should break again + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // first call after duration returns a fault, so circuit should break again - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync(); - } + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + // circuit has been reset so should once again allow 2 faults to be raised before breaking + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - // circuit has been reset so should once again allow 2 faults to be raised before breaking - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(async () => + { + await breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + await TaskHelper.EmptyTask; + firstExecutionActive = false; - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + return ResultPrimitive.Good; + })).Should().NotThrowAsync(); + }, TaskCreationOptions.LongRunning); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + Task secondExecution = Task.Factory.StartNew(async () => { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(async () => + try { - await breaker.Awaiting(x => x.ExecuteAsync(async () => + await breaker.ExecuteAsync(async () => { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); await TaskHelper.EmptyTask; - firstExecutionActive = false; return ResultPrimitive.Good; - })).Should().NotThrowAsync(); - }, TaskCreationOptions.LongRunning); + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } + } - Task secondExecution = Task.Factory.StartNew(async () => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - try - { - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); - await TaskHelper.EmptyTask; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - } - } + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(async () => + { + await breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + return ResultPrimitive.Good; + })).Should().NotThrowAsync(); + }, TaskCreationOptions.LongRunning); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + Task secondExecution = Task.Factory.StartNew(async () => { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(async () => + try { - await breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); await TaskHelper.EmptyTask; - firstExecutionActive = false; return ResultPrimitive.Good; - })).Should().NotThrowAsync(); - }, TaskCreationOptions.LongRunning); - - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - - Task secondExecution = Task.Factory.StartNew(async () => + }); + } + catch (BrokenCircuitException) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - - await TaskHelper.EmptyTask; - - return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } - - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + // Release first execution soon as second overlapping execution is done gathering data. permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); - } + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); } + } - #endregion + #endregion - #region Isolate and reset tests + #region Isolate and reset tests - [Fact] - public async Task Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit - bool delegateExecutedWhenBroken = false; - await breaker.Awaiting(b => b.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); + // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit + bool delegateExecutedWhenBroken = false; + await breaker.Awaiting(b => b.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - bool delegateExecutedWhenBroken = false; - await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) - .Should().ThrowAsync(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + bool delegateExecutedWhenBroken = false; + await breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) + .Should().ThrowAsync(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public async Task Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().ThrowAsync(); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); - } + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); + } - [Fact] - public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); - } + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); + } - #endregion + #endregion - #region State-change delegate tests + #region State-change delegate tests - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action, TimeSpan> onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action, TimeSpan> onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onResetCalled.Should().BeFalse(); - } + onResetCalled.Should().BeFalse(); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_automatically() - { - bool onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_automatically() + { + bool onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - bool onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + bool onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - breaker.Isolate(); + breaker.Isolate(); - onBreakCalled.Should().BeTrue(); - } + onBreakCalled.Should().BeTrue(); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - int onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + int onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - // call through circuit when already broken - should not retrigger onBreak - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().ThrowAsync(); + // call through circuit when already broken - should not retrigger onBreak + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + int onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - int onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + Task longRunningExecution = Task.Factory.StartNew(async () => { - Task longRunningExecution = Task.Factory.StartNew(async () => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); - - (await breaker.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; - - permitMainThreadToOpenCircuit.Set(); - - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - return ResultPrimitive.Fault; - - })).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. - }, TaskCreationOptions.LongRunning); - - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - - // Break circuit in the normal manner: onBreak() should be called once. breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); - - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } - } + (await breaker.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; - [Fact] - public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + permitMainThreadToOpenCircuit.Set(); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - var durationOfBreak = TimeSpan.FromMinutes(1); + // Throw a further failure when rest of test has already broken the circuit. + breaker.CircuitState.Should().Be(CircuitState.Open); + return ResultPrimitive.Fault; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + })).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. + }, TaskCreationOptions.LongRunning); - onBreakCalled.Should().Be(0); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); - - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().ThrowAsync(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - // first call after duration is successful, so circuit should reset - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); } + } - [Fact] - public async Task Should_not_call_onreset_on_successive_successful_calls() - { - Action, TimeSpan> onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - - onResetCalled.Should().BeFalse(); - - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + [Fact] + public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + var durationOfBreak = TimeSpan.FromMinutes(1); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + onBreakCalled.Should().Be(0); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); - onBreakCalled.Should().Be(0); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + // first call after duration is successful, so circuit should reset + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - // first call after duration is successful, so circuit should reset - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().NotThrowAsync(); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + [Fact] + public async Task Should_not_call_onreset_on_successive_successful_calls() + { + Action, TimeSpan> onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onResetCalled.Should().BeFalse(); - var durationOfBreak = TimeSpan.FromMinutes(1); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - onBreakCalled.Should().Be(0); + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); + + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + + // first call after duration is successful, so circuit should reset + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().NotThrowAsync(); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); + + // 2 exception or fault raised, circuit is now open + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); + [Fact] + public async Task Should_call_onreset_when_manually_resetting_circuit() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - // 2 exception or fault raised, circuit is now open - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_call_onreset_when_manually_resetting_circuit() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().ThrowAsync(); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().NotThrowAsync(); + } - breaker.CircuitState.Should().Be(CircuitState.Isolated); - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().ThrowAsync(); + #region Tests of supplied parameters to onBreak delegate - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + [Fact] + public async Task Should_call_onbreak_with_the_last_handled_result() + { + ResultPrimitive? handledResult = null; - breaker.CircuitState.Should().Be(CircuitState.Closed); - await breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().NotThrowAsync(); - } + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; + Action onReset = _ => { }; - #region Tests of supplied parameters to onBreak delegate + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_call_onbreak_with_the_last_handled_result() - { - ResultPrimitive? handledResult = null; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; - Action onReset = _ => { }; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + handledResult?.Should().Be(ResultPrimitive.Fault); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - handledResult?.Should().Be(ResultPrimitive.Fault); - } + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + passedBreakTimespan.Should().Be(durationOfBreak); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Open); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - passedBreakTimespan.Should().Be(durationOfBreak); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - var durationOfBreak = TimeSpan.FromMinutes(1); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + #region Tests that supplied context is passed to stage-change delegates - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + [Fact] + public async Task Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - #endregion + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - #region Tests that supplied context is passed to stage-change delegates + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public async Task Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + (await breaker.RaiseResultSequenceAsync(new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - (await breaker.RaiseResultSequenceAsync(new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action, TimeSpan, Context> onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action, TimeSpan, Context> onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + // first call after duration should invoke onReset, with context + await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key1 = "value1", key2 = "value2" }.AsDictionary()); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - // first call after duration should invoke onReset, with context - await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key1 = "value1", key2 = "value2" }.AsDictionary()); + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + contextData.Should().BeEmpty(); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - contextData.Should().BeEmpty(); - } + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // 2 exception or fault raised, circuit is now open + (await breaker.RaiseResultSequenceAsync(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - // 2 exception or fault raised, circuit is now open - (await breaker.RaiseResultSequenceAsync(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #endregion - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + #endregion - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + #region LastHandledResult property - #endregion + [Fact] + public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - #endregion + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - #region LastHandledResult property + [Fact] + public async Task Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } - [Fact] - public async Task Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } + [Fact] + public async Task Should_set_LastHandledResult_to_last_handled_result_when_breaking() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public async Task Should_set_LastHandledResult_to_last_handled_result_when_breaking() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_set_LastHandledResult_to_default_on_circuit_reset() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public async Task Should_set_LastHandledResult_to_default_on_circuit_reset() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); + #endregion - breaker.Reset(); + #region Cancellation support - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - #endregion + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - #region Cancellation support + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; - - (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + attemptsInvoked.Should().Be(1); + } - attemptsInvoked.Should().Be(1); - } + [Fact] + public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); - [Fact] - public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().ThrowAsync() + .WithMessage("The circuit is now open and is not allowing calls."); + // Circuit is now broken. - var ex = await breaker.Awaiting(x => x.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().ThrowAsync() - .WithMessage("The circuit is now open and is not allowing calls."); - // Circuit is now broken. + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + cancellationTokenSource.Cancel(); - cancellationTokenSource.Cancel(); + Scenario scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + var ex2 = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex2.And.CancellationToken.Should().Be(cancellationToken); - var ex2 = await breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex2.And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); + } - attemptsInvoked.Should().Be(0); - } + [Fact] + public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. - [Fact] - public async Task Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); - CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + implicitlyCapturedActionCancellationTokenSource.Cancel(); - implicitlyCapturedActionCancellationTokenSource.Cancel(); + int attemptsInvoked = 0; - int attemptsInvoked = 0; + var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => + { + attemptsInvoked++; + await TaskHelper.EmptyTask; + implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); + return ResultPrimitive.Good; + }, policyCancellationToken)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - var ex = await breaker.Awaiting(x => x.ExecuteAsync(async _ => - { - attemptsInvoked++; - await TaskHelper.EmptyTask; - implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); - return ResultPrimitive.Good; - }, policyCancellationToken)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion - public void Dispose() - { - SystemClock.Reset(); - } + #endregion + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs index 71151a00560..bdd320ef772 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs @@ -6,597 +6,596 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.CircuitBreaker +namespace Polly.Specs.CircuitBreaker; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class CircuitBreakerTResultMixedResultExceptionSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class CircuitBreakerTResultMixedResultExceptionSpecs : IDisposable + #region Circuit-breaker threshold-to-break tests + + [Fact] + public void Should_open_circuit_with_exception_after_specified_number_of_specified_exception_have_been_returned_when_result_policy_handling_exceptions_only() { - #region Circuit-breaker threshold-to-break tests - - [Fact] - public void Should_open_circuit_with_exception_after_specified_number_of_specified_exception_have_been_returned_when_result_policy_handling_exceptions_only() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e. Result == ResultPrimitive.Fault); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.Fault); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_open_circuit_if_results_and_exceptions_returned_match_combination_of_the_result_and_exception_predicates() - { - CircuitBreakerPolicy breaker = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_configured_results_or_exceptions() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Should_not_open_circuit_if_exception_thrown_is_not_one_of_the_configured_results_or_exceptions() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() - { - CircuitBreakerPolicy breaker = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - // non-matched result predicate - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // non-matched exception predicate - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__configuring_multiple_results_and_exceptions() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(4, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 4 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__when_configuring_multiple_results_and_exceptions() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(4, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 4 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.FaultAgain); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_not_open_circuit_if_result_raised_or_exception_thrown_is_not_one_of_the_handled_results_or_exceptions() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region Circuit-breaker open->half-open->open/closed tests - - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + CircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // first call after duration returns a fault, so circuit should break again - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e. Result == ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } - } + [Fact] + public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_open_circuit_if_results_and_exceptions_returned_match_combination_of_the_result_and_exception_predicates() + { + CircuitBreakerPolicy breaker = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_configured_results_or_exceptions() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_exception_thrown_is_not_one_of_the_configured_results_or_exceptions() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() + { + CircuitBreakerPolicy breaker = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + // non-matched result predicate + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // non-matched exception predicate + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__configuring_multiple_results_and_exceptions() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(4, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 4 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__when_configuring_multiple_results_and_exceptions() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(4, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 4 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.FaultAgain); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_not_open_circuit_if_result_raised_or_exception_thrown_is_not_one_of_the_handled_results_or_exceptions() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + #endregion - // first call after duration returns a fault, so circuit should break again - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); + #region Circuit-breaker open->half-open->open/closed tests - } + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #endregion + var durationOfBreak = TimeSpan.FromMinutes(1); - #region State-change delegate tests + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - #region Tests of supplied parameters to onBreak delegate + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_call_onbreak_with_the_last_handled_result() - { - ResultPrimitive? handledResult = null; + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; - Action onReset = _ => { }; + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + // first call after duration returns a fault, so circuit should break again + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - handledResult?.Should().Be(ResultPrimitive.Fault); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception lastException = null; + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { lastException = outcome.Exception; }; - Action onReset = _ => { }; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.CircuitState.Should().Be(CircuitState.Open); + // first call after duration returns a fault, so circuit should break again + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); - lastException.Should().BeOfType(); - } + } - #endregion + #endregion - #endregion + #region State-change delegate tests - #region LastHandledResult and LastException property + #region Tests of supplied parameters to onBreak delegate - [Fact] - public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_call_onbreak_with_the_last_handled_result() + { + ResultPrimitive? handledResult = null; - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; + Action onReset = _ => { }; - [Fact] - public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public void Should_set_LastException_on_exception_even_when_not_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + handledResult?.Should().Be(ResultPrimitive.Fault); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception lastException = null; - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeOfType(); - } + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { lastException = outcome.Exception; }; + Action onReset = _ => { }; - [Fact] - public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_set_LastException_to_last_exception_when_breaking() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + lastException.Should().BeOfType(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + #endregion - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #region LastHandledResult and LastException property - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeOfType(); - } + [Fact] + public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } + + [Fact] + public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } + + [Fact] + public void Should_set_LastException_on_exception_even_when_not_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeOfType(); + } + + [Fact] + public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } + + [Fact] + public void Should_set_LastException_to_last_exception_when_breaking() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_set_LastHandledResult_and_LastException_to_default_on_circuit_reset() - { - CircuitBreakerPolicy breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeOfType(); + } + + [Fact] + public void Should_set_LastHandledResult_and_LastException_to_default_on_circuit_reset() + { + CircuitBreakerPolicy breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); - #endregion + breaker.Reset(); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } + + #endregion - public void Dispose() - { - SystemClock.Reset(); - } + + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs index 9f0a1f49372..0516e371075 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs @@ -11,1582 +11,1581 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker -{ - [Collection(Constants.SystemClockDependentTestCollection)] - public class CircuitBreakerTResultSpecs : IDisposable - { - #region Configuration tests - - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.MaxValue); +namespace Polly.Specs.CircuitBreaker; - var result = breaker.RaiseResultSequence(ResultPrimitive.Fault); - result.Should().Be(ResultPrimitive.Fault); - } - - [Fact] - public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(0, TimeSpan.FromSeconds(10)); +[Collection(Constants.SystemClockDependentTestCollection)] +public class CircuitBreakerTResultSpecs : IDisposable +{ + #region Configuration tests - action.Should().Throw() - .And.ParamName.Should() - .Be("handledEventsAllowedBeforeBreaking"); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.MaxValue); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); + var result = breaker.RaiseResultSequence(ResultPrimitive.Fault); + result.Should().Be(ResultPrimitive.Fault); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + [Fact] + public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(0, TimeSpan.FromSeconds(10)); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.Zero); - action.Should().NotThrow(); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("handledEventsAllowedBeforeBreaking"); + } - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - #endregion + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - #region Circuit-breaker threshold-to-break tests + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - [Fact] - public void Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.RaiseResultSequence(ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #region Circuit-breaker threshold-to-break tests - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.Fault); + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); + breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public void Should_not_open_circuit_if_result_returned_is_not_the_handled_result() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_not_open_circuit_if_result_returned_is_not_the_handled_result() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultYetAgain) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultYetAgain) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + #region Circuit-breaker open->half-open->open/closed tests - [Fact] - public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Circuit-breaker open->half-open->open/closed tests + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + } - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - } + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + // first call after duration returns a fault, so circuit should break again + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // first call after duration returns a fault, so circuit should break again - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - } + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + // circuit has been reset so should once again allow 2 faults to be raised before breaking + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - // circuit has been reset so should once again allow 2 faults to be raised before breaking - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + return ResultPrimitive.Good; + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + Task secondExecution = Task.Factory.StartNew(() => { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + try { - breaker.Invoking(x => x.Execute(() => + breaker.Execute(() => { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); - - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. return ResultPrimitive.Good; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); - - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - - Task secondExecution = Task.Factory.StartNew(() => + }); + } + catch (BrokenCircuitException) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - try - { - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - - return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + // Release first execution soon as second overlapping execution is done gathering data. permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - } + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); } + } - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - bool firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - Task firstExecution = Task.Factory.StartNew(() => + bool firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + Task firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - return ResultPrimitive.Good; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + return ResultPrimitive.Good; + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - Task secondExecution = Task.Factory.StartNew(() => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + Task secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) + breaker.Execute(() => { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); + return ResultPrimitive.Good; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + // Release first execution soon as second overlapping execution is done gathering data. permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); - } + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); } + } - #endregion + #endregion - #region Isolate and reset tests + #region Isolate and reset tests - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good;})) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); + // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good;})) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - bool delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + bool delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); - } + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); + } - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); - } + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); + } - #endregion + #endregion - #region State-change delegate tests + #region State-change delegate tests - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action, TimeSpan> onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action, TimeSpan> onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onResetCalled.Should().BeFalse(); - } + onResetCalled.Should().BeFalse(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - bool onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + bool onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - bool onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - Action onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + bool onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + Action onReset = () => { }; - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - breaker.Isolate(); + breaker.Isolate(); - onBreakCalled.Should().BeTrue(); - } + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - int onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + int onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - // call through circuit when already broken - should not retrigger onBreak - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); + // call through circuit when already broken - should not retrigger onBreak + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + int onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { }; + + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - int onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { }; - - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + Task longRunningExecution = Task.Factory.StartNew(() => { - Task longRunningExecution = Task.Factory.StartNew(() => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Execute(() => - { - permitMainThreadToOpenCircuit.Set(); - - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - return ResultPrimitive.Fault; - - }).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. - }, TaskCreationOptions.LongRunning); - - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - - // Break circuit in the normal manner: onBreak() should be called once. breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + breaker.Execute(() => + { + permitMainThreadToOpenCircuit.Set(); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - // onBreak() should still only have been called once. + // Throw a further failure when rest of test has already broken the circuit. breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } - } - - [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + return ResultPrimitive.Fault; - var durationOfBreak = TimeSpan.FromMinutes(1); + }).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. + }, TaskCreationOptions.LongRunning); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); - breaker.RaiseResultSequence(ResultPrimitive.Fault) .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); } + } - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action, TimeSpan> onBreak = (_, _) => { }; - bool onResetCalled = false; - Action onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - onResetCalled.Should().BeFalse(); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + onBreakCalled.Should().Be(0); - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - onBreakCalled.Should().Be(0); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action, TimeSpan> onBreak = (_, _) => { }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + onResetCalled.Should().BeFalse(); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => ResultPrimitive.Good); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - int onBreakCalled = 0; - int onResetCalled = 0; - int onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; - Action onHalfOpen = () => { onHalfOpenCalled++; }; + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state + + // first call after duration is successful, so circuit should reset + breaker.Execute(() => ResultPrimitive.Good); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + + onBreakCalled.Should().Be(0); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + int onBreakCalled = 0; + int onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + Action onReset = () => { onResetCalled++; }; - onBreakCalled.Should().Be(0); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - int onBreakCalled = 0; - int onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - Action onReset = () => { onResetCalled++; }; + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); + } - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + #region Tests of supplied parameters to onBreak delegate - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_with_the_last_handled_result() + { + ResultPrimitive? handledResult = null; - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); - } + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - #region Tests of supplied parameters to onBreak delegate + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - [Fact] - public void Should_call_onbreak_with_the_last_handled_result() - { - ResultPrimitive? handledResult = null; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; - Action onReset = _ => { }; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Open); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + handledResult?.Should().Be(ResultPrimitive.Fault); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Open); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - handledResult?.Should().Be(ResultPrimitive.Fault); - } + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Open); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + passedBreakTimespan.Should().Be(durationOfBreak); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); - passedBreakTimespan.Should().Be(durationOfBreak); - } + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + #region Tests that supplied context is passed to stage-change delegates - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - #endregion + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - #region Tests that supplied context is passed to stage-change delegates + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + breaker.RaiseResultSequence(new {key1 = "value1", key2 = "value2"}.AsDictionary(), + ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - breaker.RaiseResultSequence(new {key1 = "value1", key2 = "value2"}.AsDictionary(), - ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action, TimeSpan, Context> onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action, TimeSpan, Context> onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + // first call after duration should invoke onReset, with context + breaker.Execute(_ => ResultPrimitive.Good, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - // first call after duration should invoke onReset, with context - breaker.Execute(_ => ResultPrimitive.Good, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + contextData.Should().BeEmpty(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - contextData.Should().BeEmpty(); - } + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // 2 exception raised, circuit is now open + breaker.RaiseResultSequence(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - var durationOfBreak = TimeSpan.FromMinutes(1); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - // 2 exception raised, circuit is now open - breaker.RaiseResultSequence(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + // first call after duration is successful, so circuit should reset + breaker.Execute(_ => ResultPrimitive.Good, new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #endregion - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + #endregion - // first call after duration is successful, so circuit should reset - breaker.Execute(_ => ResultPrimitive.Good, new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + #region LastHandledResult property - #endregion + [Fact] + public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - #endregion + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - #region LastHandledResult property + [Fact] + public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_set_LastHandledResult_to_default_on_circuit_reset() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public void Should_set_LastHandledResult_to_default_on_circuit_reset() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); + #endregion - breaker.Reset(); + #region Cancellation support - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - #endregion + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - #region Cancellation support + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() + Scenario scenario = new Scenario { - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + attemptsInvoked.Should().Be(1); + } - attemptsInvoked.Should().Be(1); - } + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.Invoking(x => x.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls."); + // Circuit is now broken. - breaker.Invoking(x => x.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls."); - // Circuit is now broken. + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + cancellationTokenSource.Cancel(); - cancellationTokenSource.Cancel(); + Scenario scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); + } - attemptsInvoked.Should().Be(0); - } + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. + var durationOfBreak = TimeSpan.FromMinutes(1); + CircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); - CircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); - CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + implicitlyCapturedActionCancellationTokenSource.Cancel(); - implicitlyCapturedActionCancellationTokenSource.Cancel(); + int attemptsInvoked = 0; - int attemptsInvoked = 0; + breaker.Invoking(x => x.Execute(_ => + { + attemptsInvoked++; + implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); + return ResultPrimitive.Good; + }, policyCancellationToken)) + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - breaker.Invoking(x => x.Execute(_ => - { - attemptsInvoked++; - implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); - return ResultPrimitive.Good; - }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs index 4854f25ba0d..a8869877448 100644 --- a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs @@ -3,56 +3,55 @@ using Polly.CircuitBreaker; using Xunit; -namespace Polly.Specs.CircuitBreaker +namespace Polly.Specs.CircuitBreaker; + +public class ICircuitBreakerPolicySpecs { - public class ICircuitBreakerPolicySpecs + [Fact] + public void Should_be_able_to_use_CircuitState_via_interface() { - [Fact] - public void Should_be_able_to_use_CircuitState_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.CircuitState.Should().Be(CircuitState.Closed); + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_be_able_to_use_Isolate_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + } - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - } + [Fact] + public void Should_be_able_to_use_Isolate_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_be_able_to_use_Reset_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + } - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + [Fact] + public void Should_be_able_to_use_Reset_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - [Fact] - public void Should_be_able_to_use_LastException_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.LastException.Should().BeNull(); + [Fact] + public void Should_be_able_to_use_LastException_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - } + breaker.LastException.Should().BeNull(); } + } diff --git a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs index f4ab982fccb..91a94bd8e22 100644 --- a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs @@ -4,20 +4,19 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.CircuitBreaker +namespace Polly.Specs.CircuitBreaker; + +public class ICircuitBreakerTResultPolicySpecs { - public class ICircuitBreakerTResultPolicySpecs + [Fact] + public void Should_be_able_to_use_LastHandledResult_via_interface() { - [Fact] - public void Should_be_able_to_use_LastHandledResult_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + ICircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - } + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); } + } diff --git a/src/Polly.Specs/ContextSpecs.cs b/src/Polly.Specs/ContextSpecs.cs index 2c557fdcf3f..dcdcf1ab036 100644 --- a/src/Polly.Specs/ContextSpecs.cs +++ b/src/Polly.Specs/ContextSpecs.cs @@ -3,55 +3,54 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class ContextSpecs { - public class ContextSpecs + [Fact] + public void Should_assign_OperationKey_from_constructor() { - [Fact] - public void Should_assign_OperationKey_from_constructor() - { - Context context = new Context("SomeKey"); + Context context = new Context("SomeKey"); - context.OperationKey.Should().Be("SomeKey"); + context.OperationKey.Should().Be("SomeKey"); - context.Keys.Count.Should().Be(0); - } + context.Keys.Count.Should().Be(0); + } - [Fact] - public void Should_assign_OperationKey_and_context_data_from_constructor() - { - Context context = new Context("SomeKey", new { key1 = "value1", key2 = "value2" }.AsDictionary()); + [Fact] + public void Should_assign_OperationKey_and_context_data_from_constructor() + { + Context context = new Context("SomeKey", new { key1 = "value1", key2 = "value2" }.AsDictionary()); - context.OperationKey.Should().Be("SomeKey"); - context["key1"].Should().Be("value1"); - context["key2"].Should().Be("value2"); - } + context.OperationKey.Should().Be("SomeKey"); + context["key1"].Should().Be("value1"); + context["key2"].Should().Be("value2"); + } - [Fact] - public void NoArgsCtor_should_assign_no_OperationKey() - { - Context context = new Context(); + [Fact] + public void NoArgsCtor_should_assign_no_OperationKey() + { + Context context = new Context(); - context.OperationKey.Should().BeNull(); - } + context.OperationKey.Should().BeNull(); + } - [Fact] - public void Should_assign_CorrelationId_when_accessed() - { - Context context = new Context("SomeKey"); + [Fact] + public void Should_assign_CorrelationId_when_accessed() + { + Context context = new Context("SomeKey"); - context.CorrelationId.Should().NotBeEmpty(); - } + context.CorrelationId.Should().NotBeEmpty(); + } - [Fact] - public void Should_return_consistent_CorrelationId() - { - Context context = new Context("SomeKey"); + [Fact] + public void Should_return_consistent_CorrelationId() + { + Context context = new Context("SomeKey"); - Guid retrieved1 = context.CorrelationId; - Guid retrieved2 = context.CorrelationId; + Guid retrieved1 = context.CorrelationId; + Guid retrieved2 = context.CorrelationId; - retrieved1.Should().Be(retrieved2); - } + retrieved1.Should().Be(retrieved2); } } diff --git a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs index 9f2c7493e11..05c7043aad4 100644 --- a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs @@ -5,97 +5,96 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom +namespace Polly.Specs.Custom; + +public class CustomAsyncSpecs { - public class CustomAsyncSpecs + [Fact] + public void Should_be_able_to_construct_active_policy() { - [Fact] - public void Should_be_able_to_construct_active_policy() + Action construct = () => { - Action construct = () => + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(async () => { - AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(async () => - { - // Placeholder for more substantive async work. - Console.WriteLine("Do something"); - await Task.CompletedTask; - }); - }; - - construct.Should().NotThrow(); - } - - [Fact] - public async Task Active_policy_should_execute() - { - bool preExecuted = false; - AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); + // Placeholder for more substantive async work. + Console.WriteLine("Do something"); + await Task.CompletedTask; + }); + }; + + construct.Should().NotThrow(); + } + + [Fact] + public async Task Active_policy_should_execute() + { + bool preExecuted = false; + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); - bool executed = false; + bool executed = false; - await policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; return Task.CompletedTask; })) - .Should().NotThrowAsync(); + await policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; return Task.CompletedTask; })) + .Should().NotThrowAsync(); - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - [Fact] - public void Should_be_able_to_construct_reactive_policy() + [Fact] + public void Should_be_able_to_construct_reactive_policy() + { + Action construct = () => { - Action construct = () => + AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { - AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => - { - // Placeholder for more substantive async work. - Console.WriteLine("Handling " + ex.Message); - await Task.CompletedTask; - }); - }; - - construct.Should().NotThrow(); - } - - [Fact] - public async Task Reactive_policy_should_handle_exception() + // Placeholder for more substantive async work. + Console.WriteLine("Handling " + ex.Message); + await Task.CompletedTask; + }); + }; + + construct.Should().NotThrow(); + } + + [Fact] + public async Task Reactive_policy_should_handle_exception() + { + Exception handled = null; + AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); + + Exception toThrow = new InvalidOperationException(); + bool executed = false; + + var ex = await policy.Awaiting(x => x.ExecuteAsync(() => { - Exception handled = null; - AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); + executed = true; + throw toThrow; + })) + .Should().ThrowAsync(); + ex.Which.Should().Be(toThrow); + + executed.Should().BeTrue(); + handled.Should().Be(toThrow); + } - Exception toThrow = new InvalidOperationException(); - bool executed = false; + [Fact] + public async Task Reactive_policy_should_be_able_to_ignore_unhandled_exception() + { + Exception handled = null; + AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); + + Exception toThrow = new NotImplementedException(); + bool executed = false; - var ex = await policy.Awaiting(x => x.ExecuteAsync(() => + var ex = await policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; throw toThrow; })) - .Should().ThrowAsync(); - ex.Which.Should().Be(toThrow); + .Should().ThrowAsync(); + ex.Which.Should().Be(toThrow); - executed.Should().BeTrue(); - handled.Should().Be(toThrow); - } - - [Fact] - public async Task Reactive_policy_should_be_able_to_ignore_unhandled_exception() - { - Exception handled = null; - AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); - - Exception toThrow = new NotImplementedException(); - bool executed = false; - - var ex = await policy.Awaiting(x => x.ExecuteAsync(() => - { - executed = true; - throw toThrow; - })) - .Should().ThrowAsync(); - ex.Which.Should().Be(toThrow); - - executed.Should().BeTrue(); - handled.Should().Be(null); - } + executed.Should().BeTrue(); + handled.Should().Be(null); } } diff --git a/src/Polly.Specs/Custom/CustomSpecs.cs b/src/Polly.Specs/Custom/CustomSpecs.cs index c98bf0dae91..86779914890 100644 --- a/src/Polly.Specs/Custom/CustomSpecs.cs +++ b/src/Polly.Specs/Custom/CustomSpecs.cs @@ -4,83 +4,82 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom +namespace Polly.Specs.Custom; + +public class CustomSpecs { - public class CustomSpecs + [Fact] + public void Should_be_able_to_construct_active_policy() { - [Fact] - public void Should_be_able_to_construct_active_policy() + Action construct = () => { - Action construct = () => - { - PreExecutePolicy policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); - }; + PreExecutePolicy policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); + }; - construct.Should().NotThrow(); - } + construct.Should().NotThrow(); + } - [Fact] - public void Active_policy_should_execute() - { - bool preExecuted = false; - PreExecutePolicy policy = PreExecutePolicy.Create(() => preExecuted = true); + [Fact] + public void Active_policy_should_execute() + { + bool preExecuted = false; + PreExecutePolicy policy = PreExecutePolicy.Create(() => preExecuted = true); - bool executed = false; + bool executed = false; - policy.Invoking(x => x.Execute(() => { executed = true; })) - .Should().NotThrow(); + policy.Invoking(x => x.Execute(() => { executed = true; })) + .Should().NotThrow(); - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - [Fact] - public void Should_be_able_to_construct_reactive_policy() + [Fact] + public void Should_be_able_to_construct_reactive_policy() + { + Action construct = () => { - Action construct = () => - { - AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => Console.WriteLine("Handling " + ex.Message)); - }; + AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => Console.WriteLine("Handling " + ex.Message)); + }; - construct.Should().NotThrow(); - } + construct.Should().NotThrow(); + } - [Fact] - public void Reactive_policy_should_handle_exception() - { - Exception handled = null; - AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => handled = ex); + [Fact] + public void Reactive_policy_should_handle_exception() + { + Exception handled = null; + AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => handled = ex); - Exception toThrow = new InvalidOperationException(); - bool executed = false; + Exception toThrow = new InvalidOperationException(); + bool executed = false; - policy.Invoking(x => x.Execute(() => { - executed = true; - throw toThrow; - })) - .Should().Throw().Which.Should().Be(toThrow); + policy.Invoking(x => x.Execute(() => { + executed = true; + throw toThrow; + })) + .Should().Throw().Which.Should().Be(toThrow); - executed.Should().BeTrue(); - handled.Should().Be(toThrow); - } + executed.Should().BeTrue(); + handled.Should().Be(toThrow); + } - [Fact] - public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() - { - Exception handled = null; - AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => handled = ex); + [Fact] + public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() + { + Exception handled = null; + AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => handled = ex); - Exception toThrow = new NotImplementedException(); - bool executed = false; + Exception toThrow = new NotImplementedException(); + bool executed = false; - policy.Invoking(x => x.Execute(() => { - executed = true; - throw toThrow; - })) - .Should().Throw().Which.Should().Be(toThrow); + policy.Invoking(x => x.Execute(() => { + executed = true; + throw toThrow; + })) + .Should().Throw().Which.Should().Be(toThrow); - executed.Should().BeTrue(); - handled.Should().Be(null); - } + executed.Should().BeTrue(); + handled.Should().Be(null); } } diff --git a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs index 539de07c8d9..cafbd25f883 100644 --- a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs @@ -6,101 +6,100 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom +namespace Polly.Specs.Custom; + +public class CustomTResultAsyncSpecs { - public class CustomTResultAsyncSpecs + [Fact] + public void Should_be_able_to_construct_active_policy() { - [Fact] - public void Should_be_able_to_construct_active_policy() + Action construct = () => { - Action construct = () => + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(async () => { - AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(async () => - { - // Placeholder for more substantive async work. - Console.WriteLine("Do something"); - await Task.CompletedTask; - }); - }; - - construct.Should().NotThrow(); - } - - [Fact] - public async Task Active_policy_should_execute() - { - bool preExecuted = false; - AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); + // Placeholder for more substantive async work. + Console.WriteLine("Do something"); + await Task.CompletedTask; + }); + }; + + construct.Should().NotThrow(); + } + + [Fact] + public async Task Active_policy_should_execute() + { + bool preExecuted = false; + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); - bool executed = false; - await policy.Awaiting(x => x.ExecuteAsync(async () => { executed = true; await Task.CompletedTask; return ResultPrimitive.Undefined; })) - .Should().NotThrowAsync(); + bool executed = false; + await policy.Awaiting(x => x.ExecuteAsync(async () => { executed = true; await Task.CompletedTask; return ResultPrimitive.Undefined; })) + .Should().NotThrowAsync(); - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - [Fact] - public void Should_be_able_to_construct_reactive_policy() + [Fact] + public void Should_be_able_to_construct_reactive_policy() + { + Action construct = () => { - Action construct = () => + AsyncAddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviourAsync(async outcome => { - AsyncAddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviourAsync(async outcome => - { - // Placeholder for more substantive async work. - Console.WriteLine("Handling " + outcome.Result); - await Task.CompletedTask; - }); - }; - - construct.Should().NotThrow(); - } - - [Fact] - public async Task Reactive_policy_should_handle_result() - { - ResultPrimitive handled = ResultPrimitive.Undefined; - AsyncAddBehaviourIfHandlePolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); - - ResultPrimitive toReturn = ResultPrimitive.Fault; - bool executed = false; - - (await policy.ExecuteAsync(async () => - { - executed = true; - await Task.CompletedTask; - return toReturn; - })) - .Should().Be(toReturn); - - executed.Should().BeTrue(); - handled.Should().Be(toReturn); - } - - [Fact] - public async Task Reactive_policy_should_be_able_to_ignore_unhandled_result() - { - ResultPrimitive? handled = null; - AsyncAddBehaviourIfHandlePolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); - - ResultPrimitive toReturn = ResultPrimitive.FaultYetAgain; - bool executed = false; - - (await policy.ExecuteAsync(async () => - { - executed = true; - await Task.CompletedTask; - return toReturn; - })) - .Should().Be(toReturn); - - executed.Should().BeTrue(); - handled.Should().Be(null); - } + // Placeholder for more substantive async work. + Console.WriteLine("Handling " + outcome.Result); + await Task.CompletedTask; + }); + }; + construct.Should().NotThrow(); } + + [Fact] + public async Task Reactive_policy_should_handle_result() + { + ResultPrimitive handled = ResultPrimitive.Undefined; + AsyncAddBehaviourIfHandlePolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); + + ResultPrimitive toReturn = ResultPrimitive.Fault; + bool executed = false; + + (await policy.ExecuteAsync(async () => + { + executed = true; + await Task.CompletedTask; + return toReturn; + })) + .Should().Be(toReturn); + + executed.Should().BeTrue(); + handled.Should().Be(toReturn); + } + + [Fact] + public async Task Reactive_policy_should_be_able_to_ignore_unhandled_result() + { + ResultPrimitive? handled = null; + AsyncAddBehaviourIfHandlePolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); + + ResultPrimitive toReturn = ResultPrimitive.FaultYetAgain; + bool executed = false; + + (await policy.ExecuteAsync(async () => + { + executed = true; + await Task.CompletedTask; + return toReturn; + })) + .Should().Be(toReturn); + + executed.Should().BeTrue(); + handled.Should().Be(null); + } + } diff --git a/src/Polly.Specs/Custom/CustomTResultSpecs.cs b/src/Polly.Specs/Custom/CustomTResultSpecs.cs index f7f43bedf9a..521e636f0c2 100644 --- a/src/Polly.Specs/Custom/CustomTResultSpecs.cs +++ b/src/Polly.Specs/Custom/CustomTResultSpecs.cs @@ -5,89 +5,88 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom +namespace Polly.Specs.Custom; + +public class CustomTResultSpecs { - public class CustomTResultSpecs + [Fact] + public void Should_be_able_to_construct_active_policy() { - [Fact] - public void Should_be_able_to_construct_active_policy() + Action construct = () => { - Action construct = () => - { - PreExecutePolicy policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); - }; + PreExecutePolicy policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); + }; - construct.Should().NotThrow(); - } + construct.Should().NotThrow(); + } - [Fact] - public void Active_policy_should_execute() - { - bool preExecuted = false; - PreExecutePolicy policy = PreExecutePolicy.Create(() => preExecuted = true); + [Fact] + public void Active_policy_should_execute() + { + bool preExecuted = false; + PreExecutePolicy policy = PreExecutePolicy.Create(() => preExecuted = true); - bool executed = false; + bool executed = false; - policy.Invoking(x => x.Execute(() => { - executed = true; - return ResultPrimitive.Undefined; - })) - .Should().NotThrow(); + policy.Invoking(x => x.Execute(() => { + executed = true; + return ResultPrimitive.Undefined; + })) + .Should().NotThrow(); - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - [Fact] - public void Should_be_able_to_construct_reactive_policy() + [Fact] + public void Should_be_able_to_construct_reactive_policy() + { + Action construct = () => { - Action construct = () => - { - AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => Console.WriteLine("Handling " + outcome.Result)); - }; - - construct.Should().NotThrow(); - } + AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => Console.WriteLine("Handling " + outcome.Result)); + }; - [Fact] - public void Reactive_policy_should_handle_result() - { - ResultPrimitive handled = ResultPrimitive.Undefined; - AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); + construct.Should().NotThrow(); + } - ResultPrimitive toReturn = ResultPrimitive.Fault; - bool executed = false; + [Fact] + public void Reactive_policy_should_handle_result() + { + ResultPrimitive handled = ResultPrimitive.Undefined; + AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); - policy.Execute(() => - { - executed = true; - return toReturn; - }) - .Should().Be(toReturn); + ResultPrimitive toReturn = ResultPrimitive.Fault; + bool executed = false; - executed.Should().BeTrue(); - handled.Should().Be(toReturn); - } + policy.Execute(() => + { + executed = true; + return toReturn; + }) + .Should().Be(toReturn); - [Fact] - public void Reactive_policy_should_be_able_to_ignore_unhandled_result() - { - ResultPrimitive? handled = null; - AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); + executed.Should().BeTrue(); + handled.Should().Be(toReturn); + } - ResultPrimitive toReturn = ResultPrimitive.FaultYetAgain; - bool executed = false; + [Fact] + public void Reactive_policy_should_be_able_to_ignore_unhandled_result() + { + ResultPrimitive? handled = null; + AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); - policy.Execute(() => - { - executed = true; - return toReturn; - }) - .Should().Be(toReturn); + ResultPrimitive toReturn = ResultPrimitive.FaultYetAgain; + bool executed = false; - executed.Should().BeTrue(); - handled.Should().Be(null); - } + policy.Execute(() => + { + executed = true; + return toReturn; + }) + .Should().Be(toReturn); + executed.Should().BeTrue(); + handled.Should().Be(null); } + } diff --git a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs index 5989fb1099d..27a4c5dbfd4 100644 --- a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs @@ -9,820 +9,819 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Fallback +namespace Polly.Specs.Fallback; + +public class FallbackAsyncSpecs { - public class FallbackAsyncSpecs - { - #region Configuration guard condition tests + #region Configuration guard condition tests - [Fact] - public void Should_throw_when_fallback_func_is_null() - { - Func fallbackActionAsync = null; + [Fact] + public void Should_throw_when_fallback_func_is_null() + { + Func fallbackActionAsync = null; - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - [Fact] - public void Should_throw_when_fallback_func_is_null_with_onFallback() - { - Func fallbackActionAsync = null; - Func onFallbackAsync = _ => TaskHelper.EmptyTask; + [Fact] + public void Should_throw_when_fallback_func_is_null_with_onFallback() + { + Func fallbackActionAsync = null; + Func onFallbackAsync = _ => TaskHelper.EmptyTask; - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - [Fact] - public void Should_throw_when_fallback_func_is_null_with_onFallback_with_context() - { - Func fallbackActionAsync = null; - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + [Fact] + public void Should_throw_when_fallback_func_is_null_with_onFallback_with_context() + { + Func fallbackActionAsync = null; + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - Func fallbackActionAsync = _ => TaskHelper.EmptyTask; - Func onFallbackAsync = null; + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + Func fallbackActionAsync = _ => TaskHelper.EmptyTask; + Func onFallbackAsync = null; - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - Func onFallbackAsync = null; + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = null; - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - #endregion + #endregion - #region Policy operation tests + #region Policy operation tests - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_does_not_throw() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_does_not_throw() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); + await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle(_ => false) - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle(_ => false) + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle(_ => false) - .Or(_ => false) - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle(_ => false) + .Or(_ => false) + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().ThrowAsync(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_execute_fallback_when_exception_thrown_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_execute_fallback_when_exception_thrown_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle(_ => true) - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle(_ => true) + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle(_ => true) - .Or() - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle(_ => true) + .Or() + .FallbackAsync(fallbackActionAsync); - await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); + await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => - { - fallbackActionExecuted = true; - throw new DivideByZeroException { HelpLink = "FromFallbackAction" }; - }; - - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); - - var ex = await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync((e, _) => e.HelpLink = "FromExecuteDelegate")) - .Should().ThrowAsync(); - ex.And.HelpLink.Should().Be("FromFallbackAction"); - - fallbackActionExecuted.Should().BeTrue(); - } - - [Fact] - public async Task Should_throw_for_generic_method_execution_on_non_generic_policy() + [Fact] + public async Task Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(_ => TaskHelper.EmptyTask); + fallbackActionExecuted = true; + throw new DivideByZeroException { HelpLink = "FromFallbackAction" }; + }; - await fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))).Should().ThrowAsync(); - } + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - #endregion + var ex = await fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync((e, _) => e.HelpLink = "FromExecuteDelegate")) + .Should().ThrowAsync(); + ex.And.HelpLink.Should().Be("FromFallbackAction"); - #region onPolicyEvent delegate tests + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_call_onFallback_passing_exception_triggering_fallback() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_throw_for_generic_method_execution_on_non_generic_policy() + { + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(_ => TaskHelper.EmptyTask); - Exception exceptionPassedToOnFallback = null; - Func onFallbackAsync = ex => { exceptionPassedToOnFallback = ex; return TaskHelper.EmptyTask; }; + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))).Should().ThrowAsync(); + } - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + #endregion - Exception instanceToThrow = new ArgumentNullException("myParam"); - await fallbackPolicy.RaiseExceptionAsync(instanceToThrow); + #region onPolicyEvent delegate tests - fallbackActionExecuted.Should().BeTrue(); - exceptionPassedToOnFallback.Should().BeOfType(); - exceptionPassedToOnFallback.Should().Be(instanceToThrow); - } + [Fact] + public async Task Should_call_onFallback_passing_exception_triggering_fallback() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Should_not_call_onFallback_when_executed_delegate_does_not_throw() - { - Func fallbackActionAsync = _ => TaskHelper.EmptyTask; + Exception exceptionPassedToOnFallback = null; + Func onFallbackAsync = ex => { exceptionPassedToOnFallback = ex; return TaskHelper.EmptyTask; }; - bool onFallbackExecuted = false; - Func onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + Exception instanceToThrow = new ArgumentNullException("myParam"); + await fallbackPolicy.RaiseExceptionAsync(instanceToThrow); - await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); + fallbackActionExecuted.Should().BeTrue(); + exceptionPassedToOnFallback.Should().BeOfType(); + exceptionPassedToOnFallback.Should().Be(instanceToThrow); + } - onFallbackExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_not_call_onFallback_when_executed_delegate_does_not_throw() + { + Func fallbackActionAsync = _ => TaskHelper.EmptyTask; - #endregion + bool onFallbackExecuted = false; + Func onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; - #region Context passing tests + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public async Task Should_call_onFallback_with_the_passed_context() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); - IDictionary contextData = null; + onFallbackExecuted.Should().BeFalse(); + } - Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + #endregion - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + #region Context passing tests - await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_onFallback_with_the_passed_context() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + IDictionary contextData = null; - [Fact] - public async Task Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - IDictionary contextData = null; + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + IDictionary contextData = null; - [Fact] - public async Task Should_call_onFallback_with_independent_context_for_independent_calls() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - IDictionary contextData = new Dictionary(); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - Func onFallbackAsync = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; return TaskHelper.EmptyTask; }; + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key = "value1" }.AsDictionary())) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_onFallback_with_independent_context_for_independent_calls() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new DivideByZeroException(), new { key = "value2" }.AsDictionary())) - .Should().NotThrowAsync(); + IDictionary contextData = new Dictionary(); - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(typeof(ArgumentNullException)); - contextData.Keys.Should().Contain(typeof(DivideByZeroException)); - contextData[typeof(ArgumentNullException)].Should().Be("value1"); - contextData[typeof(DivideByZeroException)].Should().Be("value2"); + Func onFallbackAsync = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; return TaskHelper.EmptyTask; }; - } + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool onFallbackExecuted = false; + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key = "value1" }.AsDictionary())) + .Should().NotThrowAsync(); - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - Func onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new DivideByZeroException(), new { key = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(typeof(ArgumentNullException)); + contextData.Keys.Should().Contain(typeof(DivideByZeroException)); + contextData[typeof(ArgumentNullException)].Should().Be("value1"); + contextData[typeof(DivideByZeroException)].Should().Be("value2"); - await fallbackPolicy.RaiseExceptionAsync(); + } - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool onFallbackExecuted = false; - [Fact] - public async Task Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; - Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + await fallbackPolicy.RaiseExceptionAsync(); - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool fallbackExecuted = false; + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - Func fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - await fallbackPolicy.RaiseExceptionAsync(); + [Fact] + public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool fallbackExecuted = false; - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + Func fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; - #endregion + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - #region Exception passing tests + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public async Task Should_call_fallbackAction_with_the_exception() - { - Exception fallbackException = null; + await fallbackPolicy.RaiseExceptionAsync(); - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - Func onFallback = (_, _) => TaskHelper.EmptyTask; + #endregion - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackFunc, onFallback); + #region Exception passing tests - Exception instanceToThrow = new ArgumentNullException("myParam"); - await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_fallbackAction_with_the_exception() + { + Exception fallbackException = null; - fallbackException.Should().Be(instanceToThrow); - } + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Should_call_fallbackAction_with_the_exception_when_execute_and_capture() - { - Exception fallbackException = null; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackFunc, onFallback); - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Exception instanceToThrow = new ArgumentNullException("myParam"); + await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackFunc, onFallback); + fallbackException.Should().Be(instanceToThrow); + } - await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(() => throw new ArgumentNullException())) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_fallbackAction_with_the_exception_when_execute_and_capture() + { + Exception fallbackException = null; - fallbackException.Should().NotBeNull() - .And.BeOfType(typeof(ArgumentNullException)); - } + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() - { - Exception fallbackException = null; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackFunc, onFallback); - Func onFallback = (_, _) => TaskHelper.EmptyTask; + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(() => throw new ArgumentNullException())) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .HandleInner() - .FallbackAsync(fallbackFunc, onFallback); + fallbackException.Should().NotBeNull() + .And.BeOfType(typeof(ArgumentNullException)); + } - Exception instanceToCapture = new ArgumentNullException("myParam"); - Exception instanceToThrow = new Exception(String.Empty, instanceToCapture); - await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() + { + Exception fallbackException = null; - fallbackException.Should().Be(instanceToCapture); - } + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() - { - Exception fallbackException = null; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .HandleInner() + .FallbackAsync(fallbackFunc, onFallback); - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Exception instanceToCapture = new ArgumentNullException("myParam"); + Exception instanceToThrow = new Exception(String.Empty, instanceToCapture); + await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .HandleInner() - .FallbackAsync(fallbackFunc, onFallback); + fallbackException.Should().Be(instanceToCapture); + } - Exception instanceToCapture = new ArgumentNullException("myParam"); - Exception instanceToThrow = new AggregateException(instanceToCapture); - await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() + { + Exception fallbackException = null; - fallbackException.Should().Be(instanceToCapture); - } + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() - { - Exception fallbackException = null; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + var fallbackPolicy = Policy + .HandleInner() + .FallbackAsync(fallbackFunc, onFallback); - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Exception instanceToCapture = new ArgumentNullException("myParam"); + Exception instanceToThrow = new AggregateException(instanceToCapture); + await fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrowAsync(); - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackFunc, onFallback); + fallbackException.Should().Be(instanceToCapture); + } - await fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => throw new ArgumentNullException())) - .Should().ThrowAsync(); + [Fact] + public async Task Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() + { + Exception fallbackException = null; - fallbackException.Should().BeNull(); - } + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - #endregion + Func onFallback = (_, _) => TaskHelper.EmptyTask; - #region Cancellation tests + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackFunc, onFallback); - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + await fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => throw new ArgumentNullException())) + .Should().ThrowAsync(); - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + fallbackException.Should().BeNull(); + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + #endregion - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation tests - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - [Fact] - public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, + }; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeTrue(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - cancellationTokenSource.Cancel(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - fallbackActionExecuted.Should().BeFalse(); + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - } + cancellationTokenSource.Cancel(); - [Fact] - public async Task Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() - { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + fallbackActionExecuted.Should().BeFalse(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var policy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + { + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); + attemptsInvoked.Should().Be(1); - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + fallbackActionExecuted.Should().BeTrue(); + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + [Fact] + public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + { - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); + attemptsInvoked.Should().Be(1); + fallbackActionExecuted.Should().BeFalse(); + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + [Fact] + public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + { - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - bool fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + attemptsInvoked.Should().Be(1); + fallbackActionExecuted.Should().BeFalse(); + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + [Fact] + public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + { - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + bool fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - #endregion + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); + attemptsInvoked.Should().Be(1); + fallbackActionExecuted.Should().BeTrue(); } + + #endregion + + } diff --git a/src/Polly.Specs/Fallback/FallbackSpecs.cs b/src/Polly.Specs/Fallback/FallbackSpecs.cs index de3f4d28856..6feb3e1e8a3 100644 --- a/src/Polly.Specs/Fallback/FallbackSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackSpecs.cs @@ -8,1269 +8,1268 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensions.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Fallback +namespace Polly.Specs.Fallback; + +public class FallbackSpecs { - public class FallbackSpecs + #region Configuration guard condition tests + + [Fact] + public void Should_throw_when_fallback_action_is_null() { - #region Configuration guard condition tests + Action fallbackAction = null; - [Fact] - public void Should_throw_when_fallback_action_is_null() - { - Action fallbackAction = null; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null() + { + Action fallbackAction = null; - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null() - { - Action fallbackAction = null; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback() + { + Action fallbackAction = null; + Action onFallback = _ => { }; - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback() - { - Action fallbackAction = null; - Action onFallback = _ => { }; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() + { + Action fallbackAction = null; + Action onFallback = _ => { }; - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() - { - Action fallbackAction = null; - Action onFallback = _ => { }; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() + { + Action fallbackAction = null; + Action onFallback = (_, _) => { }; - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() - { - Action fallbackAction = null; - Action onFallback = (_, _) => { }; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() + { + Action fallbackAction = null; + Action onFallback = (_, _) => { }; - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() - { - Action fallbackAction = null; - Action onFallback = (_, _) => { }; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + Action fallbackAction = () => { }; + Action onFallback = null; - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - Action fallbackAction = () => { }; - Action onFallback = null; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() + { + Action fallbackAction = _ => { }; + Action onFallback = null; - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() - { - Action fallbackAction = _ => { }; - Action onFallback = null; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Action fallbackAction = _ => { }; + Action onFallback = null; - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Action fallbackAction = _ => { }; - Action onFallback = null; + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); + + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() + { + Action fallbackAction = (_, _) => { }; + Action onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() - { - Action fallbackAction = (_, _) => { }; - Action onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + #endregion - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + #region Policy operation tests - #endregion + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_does_not_throw() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - #region Policy operation tests + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_does_not_throw() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Execute(() => { }); - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackPolicy.Execute(() => { }); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackActionExecuted.Should().BeFalse(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - fallbackActionExecuted.Should().BeFalse(); - } + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeTrue(); + } - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackActionExecuted.Should().BeTrue(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeTrue(); + } - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackActionExecuted.Should().BeTrue(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + [Fact] + public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackActionExecuted.Should().BeFalse(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle(_ => false) + .Fallback(fallbackAction); - [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - FallbackPolicy fallbackPolicy = Policy - .Handle(_ => false) - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + [Fact] + public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackActionExecuted.Should().BeFalse(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle(_ => false) + .Or(_ => false) + .Fallback(fallbackAction); - [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - FallbackPolicy fallbackPolicy = Policy - .Handle(_ => false) - .Or(_ => false) - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + [Fact] + public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackActionExecuted.Should().BeFalse(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle(_ => true) + .Fallback(fallbackAction); - [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - FallbackPolicy fallbackPolicy = Policy - .Handle(_ => true) - .Fallback(fallbackAction); + fallbackActionExecuted.Should().BeTrue(); + } - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + [Fact] + public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; + + FallbackPolicy fallbackPolicy = Policy + .Handle(_ => true) + .Or() + .Fallback(fallbackAction); + + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() + [Fact] + public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted = true; + throw new DivideByZeroException {HelpLink = "FromFallbackAction"}; + }; - FallbackPolicy fallbackPolicy = Policy - .Handle(_ => true) - .Or() - .Fallback(fallbackAction); + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException((e, _) => e.HelpLink = "FromExecuteDelegate")) + .Should().Throw().And.HelpLink.Should().Be("FromFallbackAction"); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => - { - fallbackActionExecuted = true; - throw new DivideByZeroException {HelpLink = "FromFallbackAction"}; - }; + [Fact] + public void Should_throw_for_generic_method_execution_on_non_generic_policy() + { + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(() => {}); - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + fallbackPolicy.Invoking(p => p.Execute(() => 0)).Should().Throw(); + } - fallbackPolicy.Invoking(x => x.RaiseException((e, _) => e.HelpLink = "FromExecuteDelegate")) - .Should().Throw().And.HelpLink.Should().Be("FromFallbackAction"); + #endregion - fallbackActionExecuted.Should().BeTrue(); - } + #region HandleInner tests, inner of normal exceptions - [Fact] - public void Should_throw_for_generic_method_execution_on_non_generic_policy() - { - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(() => {}); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_where_policy_doesnt_handle_inner() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(p => p.Execute(() => 0)).Should().Throw(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - #endregion + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); - #region HandleInner tests, inner of normal exceptions + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_where_policy_doesnt_handle_inner() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_policy_handles_inner_and_executed_delegate_throws_as_non_inner() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new Exception(String.Empty, new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + Exception nonInner = new DivideByZeroException(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException(nonInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_policy_handles_inner_and_executed_delegate_throws_as_non_inner() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_inner_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception nonInner = new DivideByZeroException(); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(nonInner)).Should().NotThrow(); + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_inner_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_exceptions_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new Exception(String.Empty, new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .Handle() + .OrInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new Exception(String.Empty, new ArgumentException()); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_exceptions_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .Handle() - .OrInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new Exception(String.Empty, new ArgumentException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); - Exception withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_not_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + Exception withInner = new Exception(String.Empty, new ArgumentException()); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_not_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_inner_exception_thrown_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new Exception(String.Empty, new ArgumentException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_inner_exception_thrown_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_inner_nested_exception_thrown_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new Exception(String.Empty, new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_inner_nested_exception_thrown_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); - Exception withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); + [Fact] + public void Should_execute_fallback_when_inner_exception_thrown_matches_one_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => true) + .OrInner(_ => true) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + Exception withInner = new Exception(String.Empty, new ArgumentNullException()); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_inner_exception_thrown_matches_one_of_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => true) - .OrInner(_ => true) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new Exception(String.Empty, new ArgumentNullException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => false) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - [Fact] - public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => false) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_any_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new Exception(String.Empty, new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => false) + .OrInner(_ => false) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + Exception withInner = new Exception(String.Empty, new ArgumentNullException()); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - [Fact] - public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_any_of_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => false) - .OrInner(_ => false) - .Fallback(fallbackAction); + #endregion - Exception withInner = new Exception(String.Empty, new ArgumentNullException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + #region HandleInner tests, inner of aggregate exceptions - fallbackActionExecuted.Should().BeFalse(); - } + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_where_policy_doesnt_handle_inner() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - #endregion + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); + Exception withInner = new AggregateException(new DivideByZeroException()); - #region HandleInner tests, inner of aggregate exceptions + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_where_policy_doesnt_handle_inner() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); + Exception withInner = new AggregateException(new DivideByZeroException()); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_of_aggregate_exceptions_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .Handle() + .OrInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new AggregateException(new ArgumentException()); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_of_aggregate_exceptions_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .Handle() - .OrInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_aggregate_exception_with_inner_handled_by_policy_amongst_other_inners() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new ArgumentException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new AggregateException(new ArgumentException(), new DivideByZeroException(), new ArgumentNullException()); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_aggregate_exception_with_inner_handled_by_policy_amongst_other_inners() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_of_aggregate_exception_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new ArgumentException(), new DivideByZeroException(), new ArgumentNullException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_of_aggregate_exception_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_not_handled_by_policy() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new AggregateException(new ArgumentException()); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentException); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_not_handled_by_policy() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new ArgumentException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentException); + Exception withInner = new AggregateException(new DivideByZeroException()); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_inner_of_aggregate_nested_exception_thrown_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_inner_of_aggregate_nested_exception_thrown_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); - Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); + [Fact] + public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_one_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => true) + .OrInner(_ => true) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + Exception withInner = new AggregateException(new ArgumentNullException()); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_one_of_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => true) - .OrInner(_ => true) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new ArgumentNullException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => false) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + Exception withInner = new AggregateException(new DivideByZeroException()); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); - [Fact] - public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => false) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_any_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Exception withInner = new AggregateException(new DivideByZeroException()); + FallbackPolicy fallbackPolicy = Policy + .HandleInner(_ => false) + .OrInner(_ => false) + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); + Exception withInner = new AggregateException(new ArgumentNullException()); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentNullException); - [Fact] - public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_any_of_handling_predicates() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleInner(_ => false) - .OrInner(_ => false) - .Fallback(fallbackAction); + #endregion - Exception withInner = new AggregateException(new ArgumentNullException()); + #region onPolicyEvent delegate tests - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentNullException); + [Fact] + public void Should_call_onFallback_passing_exception_triggering_fallback() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackActionExecuted.Should().BeFalse(); - } + Exception exceptionPassedToOnFallback = null; + Action onFallback = ex => { exceptionPassedToOnFallback = ex; }; - #endregion + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - #region onPolicyEvent delegate tests + Exception instanceToThrow = new ArgumentNullException("myParam"); + fallbackPolicy.RaiseException(instanceToThrow); - [Fact] - public void Should_call_onFallback_passing_exception_triggering_fallback() - { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + exceptionPassedToOnFallback.Should().BeOfType(); + exceptionPassedToOnFallback.Should().Be(instanceToThrow); + } - Exception exceptionPassedToOnFallback = null; - Action onFallback = ex => { exceptionPassedToOnFallback = ex; }; + [Fact] + public void Should_not_call_onFallback_when_executed_delegate_does_not_throw() + { + Action fallbackAction = () => { }; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + bool onFallbackExecuted = false; + Action onFallback = _ => { onFallbackExecuted = true; }; - Exception instanceToThrow = new ArgumentNullException("myParam"); - fallbackPolicy.RaiseException(instanceToThrow); + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - fallbackActionExecuted.Should().BeTrue(); - exceptionPassedToOnFallback.Should().BeOfType(); - exceptionPassedToOnFallback.Should().Be(instanceToThrow); - } + fallbackPolicy.Execute(() => { }); - [Fact] - public void Should_not_call_onFallback_when_executed_delegate_does_not_throw() - { - Action fallbackAction = () => { }; + onFallbackExecuted.Should().BeFalse(); + } - bool onFallbackExecuted = false; - Action onFallback = _ => { onFallbackExecuted = true; }; + #endregion - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + #region Context passing tests - fallbackPolicy.Execute(() => { }); + [Fact] + public void Should_call_onFallback_with_the_passed_context() + { + Action fallbackAction = _ => { }; - onFallbackExecuted.Should().BeFalse(); - } + IDictionary contextData = null; - #endregion + Action onFallback = (_, ctx) => { contextData = ctx; }; - #region Context passing tests + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_onFallback_with_the_passed_context() - { - Action fallbackAction = _ => { }; + fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), + new {key1 = "value1", key2 = "value2"}.AsDictionary())) + .Should().NotThrow(); - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action onFallback = (_, ctx) => { contextData = ctx; }; + [Fact] + public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Action fallbackAction = _ => { }; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + IDictionary contextData = null; - fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), - new {key1 = "value1", key2 = "value2"}.AsDictionary())) - .Should().NotThrow(); + Action onFallback = (_, ctx) => { contextData = ctx; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Action fallbackAction = _ => { }; + fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), + new {key1 = "value1", key2 = "value2"}.AsDictionary())) + .Should().NotThrow(); - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action onFallback = (_, ctx) => { contextData = ctx; }; + [Fact] + public void Should_call_onFallback_with_independent_context_for_independent_calls() + { + Action fallbackAction = _ => { }; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + IDictionary contextData = new Dictionary(); - fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), - new {key1 = "value1", key2 = "value2"}.AsDictionary())) - .Should().NotThrow(); + Action onFallback = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() - { - Action fallbackAction = _ => { }; + fallbackPolicy.Invoking( + p => p.Execute(_ => throw new ArgumentNullException(), new {key = "value1"}.AsDictionary())) + .Should().NotThrow(); - IDictionary contextData = new Dictionary(); + fallbackPolicy.Invoking( + p => p.Execute(_ => throw new DivideByZeroException(), new {key = "value2"}.AsDictionary())) + .Should().NotThrow(); - Action onFallback = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; }; + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(typeof (ArgumentNullException)); + contextData.Keys.Should().Contain(typeof (DivideByZeroException)); + contextData[typeof (ArgumentNullException)].Should().Be("value1"); + contextData[typeof (DivideByZeroException)].Should().Be("value2"); - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction, onFallback); + } - fallbackPolicy.Invoking( - p => p.Execute(_ => throw new ArgumentNullException(), new {key = "value1"}.AsDictionary())) - .Should().NotThrow(); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool onFallbackExecuted = false; - fallbackPolicy.Invoking( - p => p.Execute(_ => throw new DivideByZeroException(), new {key = "value2"}.AsDictionary())) - .Should().NotThrow(); + Action fallbackAction = _ => { }; + Action onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(typeof (ArgumentNullException)); - contextData.Keys.Should().Contain(typeof (DivideByZeroException)); - contextData[typeof (ArgumentNullException)].Should().Be("value1"); - contextData[typeof (DivideByZeroException)].Should().Be("value2"); + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction, onFallback); - } + fallbackPolicy.RaiseException(); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool onFallbackExecuted = false; + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - Action fallbackAction = _ => { }; - Action onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction, onFallback); + Action fallbackAction = (ctx, _) => { contextData = ctx;}; - fallbackPolicy.RaiseException(); + Action onFallback = (_, _) => { }; - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - Action fallbackAction = (ctx, _) => { contextData = ctx;}; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action onFallback = (_, _) => { }; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + Action fallbackAction = (ctx, _) => { contextData = ctx; }; - fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + Action onFallback = (_, _) => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - Action fallbackAction = (ctx, _) => { contextData = ctx; }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action onFallback = (_, _) => { }; + [Fact] + public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool fallbackExecuted = false; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + Action fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; }; + Action onFallback = (_, _) => {}; - fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction, onFallback); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + fallbackPolicy.RaiseException(); - [Fact] - public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool fallbackExecuted = false; + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - Action fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; }; - Action onFallback = (_, _) => {}; + #endregion - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction, onFallback); + #region Exception passing tests - fallbackPolicy.RaiseException(); + [Fact] + public void Should_call_fallbackAction_with_the_exception() + { + Exception fallbackException = null; - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - #endregion + Action onFallback = (_, _) => { }; - #region Exception passing tests + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_exception() - { - Exception fallbackException = null; + Exception instanceToThrow = new ArgumentNullException("myParam"); + fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) + .Should().NotThrow(); - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + fallbackException.Should().Be(instanceToThrow); + } - Action onFallback = (_, _) => { }; + [Fact] + public void Should_call_fallbackAction_with_the_exception_when_execute_and_capture() + { + Exception fallbackException = null; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - Exception instanceToThrow = new ArgumentNullException("myParam"); - fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) - .Should().NotThrow(); + Action onFallback = (_, _) => { }; - fallbackException.Should().Be(instanceToThrow); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); + fallbackPolicy.Invoking(p => p.ExecuteAndCapture(() => throw new ArgumentNullException())) + .Should().NotThrow(); - [Fact] - public void Should_call_fallbackAction_with_the_exception_when_execute_and_capture() - { - Exception fallbackException = null; + fallbackException.Should().NotBeNull() + .And.BeOfType(typeof(ArgumentNullException)); + } - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + [Fact] + public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() + { + Exception fallbackException = null; - Action onFallback = (_, _) => { }; + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); - fallbackPolicy.Invoking(p => p.ExecuteAndCapture(() => throw new ArgumentNullException())) - .Should().NotThrow(); + Action onFallback = (_, _) => { }; - fallbackException.Should().NotBeNull() - .And.BeOfType(typeof(ArgumentNullException)); - } + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() - { - Exception fallbackException = null; + Exception instanceToCapture = new ArgumentNullException("myParam"); + Exception instanceToThrow = new Exception(String.Empty, instanceToCapture); + fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) + .Should().NotThrow(); - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + fallbackException.Should().Be(instanceToCapture); + } - Action onFallback = (_, _) => { }; + [Fact] + public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() + { + Exception fallbackException = null; - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction, onFallback); + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - Exception instanceToCapture = new ArgumentNullException("myParam"); - Exception instanceToThrow = new Exception(String.Empty, instanceToCapture); - fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) - .Should().NotThrow(); + Action onFallback = (_, _) => { }; - fallbackException.Should().Be(instanceToCapture); - } + FallbackPolicy fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() - { - Exception fallbackException = null; + Exception instanceToCapture = new ArgumentNullException("myParam"); + Exception instanceToThrow = new AggregateException(instanceToCapture); + fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) + .Should().NotThrow(); - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + fallbackException.Should().Be(instanceToCapture); + } - Action onFallback = (_, _) => { }; + [Fact] + public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() + { + Exception fallbackException = null; - FallbackPolicy fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction, onFallback); + Action fallbackAction = (ex, _, _) => { + fallbackException = ex; }; - Exception instanceToCapture = new ArgumentNullException("myParam"); - Exception instanceToThrow = new AggregateException(instanceToCapture); - fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) - .Should().NotThrow(); + Action onFallback = (_, _) => { }; - fallbackException.Should().Be(instanceToCapture); - } + FallbackPolicy fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() - { - Exception fallbackException = null; + fallbackPolicy.Invoking(p => p.Execute(() => throw new ArgumentNullException())) + .Should().Throw(); - Action fallbackAction = (ex, _, _) => { - fallbackException = ex; }; + fallbackException.Should().BeNull(); + } - Action onFallback = (_, _) => { }; + #endregion - FallbackPolicy fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + #region Cancellation tests - fallbackPolicy.Invoking(p => p.Execute(() => throw new ArgumentNullException())) - .Should().Throw(); + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackException.Should().BeNull(); - } + FallbackPolicy policy = Policy + .Handle() + .Fallback(fallbackAction); - #endregion + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - #region Cancellation tests + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - FallbackPolicy policy = Policy - .Handle() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + FallbackPolicy policy = Policy + .Handle() + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, + }; - FallbackPolicy policy = Policy - .Handle() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeTrue(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, - }; + FallbackPolicy policy = Policy + .Handle() + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - FallbackPolicy policy = Policy - .Handle() - .Fallback(fallbackAction); + cancellationTokenSource.Cancel(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + FallbackPolicy policy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - FallbackPolicy policy = Policy - .Handle() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + FallbackPolicy policy = Policy + .Handle() + .Or() + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - FallbackPolicy policy = Policy - .Handle() - .Or() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeTrue(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + FallbackPolicy policy = Policy + .Handle() + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - FallbackPolicy policy = Policy - .Handle() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + FallbackPolicy policy = Policy + .Handle() + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - FallbackPolicy policy = Policy - .Handle() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + { + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); - attemptsInvoked.Should().Be(1); + FallbackPolicy policy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Action fallbackAction = () => { fallbackActionExecuted = true; }; + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - FallbackPolicy policy = Policy - .Handle() - .Fallback(fallbackAction); - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; - - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); - - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs index f5bd03da6d0..cf62109483b 100644 --- a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs @@ -9,819 +9,818 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.Fallback +namespace Polly.Specs.Fallback; + +public class FallbackTResultAsyncSpecs { - public class FallbackTResultAsyncSpecs + #region Configuration guard condition tests + + [Fact] + public void Should_throw_when_fallback_action_is_null() { - #region Configuration guard condition tests + Func> fallbackAction = null; - [Fact] - public void Should_throw_when_fallback_action_is_null() - { - Func> fallbackAction = null; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback() + { + Func> fallbackAction = null; + Func, Task> onFallbackAsync = _ => TaskHelper.EmptyTask; - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback() - { - Func> fallbackAction = null; - Func, Task> onFallbackAsync = _ => TaskHelper.EmptyTask; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() + { + Func> fallbackAction = null; + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() - { - Func> fallbackAction = null; - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); + Func, Task> onFallbackAsync = null; - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); - Func, Task> onFallbackAsync = null; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() + { + Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); + Func, Task> onFallbackAsync = null; - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() - { - Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); - Func, Task> onFallbackAsync = null; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = null; - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - Func, Context, Task> onFallbackAsync = null; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = null; - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - Func, Context, Task> onFallbackAsync = null; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + #endregion - #endregion + #region Policy operation tests - #region Policy operation tests + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackActionExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackActionExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() + { + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(ResultPrimitive.Substitute); - [Fact] - public async Task Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() - { - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(ResultPrimitive.Substitute); + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); + } - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); - } + [Fact] + public async Task Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); + fallbackActionExecuted.Should().BeTrue(); + } - fallbackActionExecuted.Should().BeTrue(); - } + [Fact] + public async Task Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.Substitute); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.Substitute); + fallbackActionExecuted.Should().BeTrue(); + } - fallbackActionExecuted.Should().BeTrue(); - } + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) + .Should().Be(ResultPrimitive.FaultYetAgain); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) - .Should().Be(ResultPrimitive.FaultYetAgain); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackActionExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(_ => false) + .FallbackAsync(fallbackAction); - var fallbackPolicy = Policy - .HandleResult(_ => false) - .FallbackAsync(fallbackAction); + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackActionExecuted.Should().BeFalse(); - } + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(r => r == ResultPrimitive.Fault) + .OrResult(r => r == ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - var fallbackPolicy = Policy - .HandleResult(r => r == ResultPrimitive.Fault) - .OrResult(r => r == ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) + .Should().Be(ResultPrimitive.FaultYetAgain); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) - .Should().Be(ResultPrimitive.FaultYetAgain); + fallbackActionExecuted.Should().BeFalse(); + } + + [Fact] + public async Task Should_execute_fallback_when_result_raised_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - fallbackActionExecuted.Should().BeFalse(); - } + var fallbackPolicy = Policy + .HandleResult(_ => true) + .FallbackAsync(fallbackAction); + + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) + .Should().Be(ResultPrimitive.Substitute); + + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_execute_fallback_when_result_raised_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - var fallbackPolicy = Policy - .HandleResult(_ => true) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) - .Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(_ => true) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) + .Should().Be(ResultPrimitive.Substitute); + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() + [Fact] + public async Task Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackActionExecuted = true; + return Task.FromResult(new ResultClass(ResultPrimitive.Fault, "FromFallbackAction")); + }; - var fallbackPolicy = Policy - .HandleResult(_ => true) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) - .Should().Be(ResultPrimitive.Substitute); + (await fallbackPolicy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate"))) + .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => - { - fallbackActionExecuted = true; - return Task.FromResult(new ResultClass(ResultPrimitive.Fault, "FromFallbackAction")); - }; + #endregion - var fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + #region onPolicyEvent delegate tests - (await fallbackPolicy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate"))) - .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); + [Fact] + public async Task Should_call_onFallback_passing_result_triggering_fallback() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(new ResultClass(ResultPrimitive.Substitute)); }; - fallbackActionExecuted.Should().BeTrue(); - } + ResultClass resultPassedToOnFallback = null; + Func, Task> onFallbackAsync = r => { resultPassedToOnFallback = r.Result; return TaskHelper.EmptyTask; }; - #endregion + var fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - #region onPolicyEvent delegate tests + ResultClass resultFromDelegate = new ResultClass(ResultPrimitive.Fault); + await fallbackPolicy.ExecuteAsync(() => Task.FromResult(resultFromDelegate)); - [Fact] - public async Task Should_call_onFallback_passing_result_triggering_fallback() - { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(new ResultClass(ResultPrimitive.Substitute)); }; + fallbackActionExecuted.Should().BeTrue(); + resultPassedToOnFallback.Should().NotBeNull(); + resultPassedToOnFallback.Should().Be(resultFromDelegate); + } - ResultClass resultPassedToOnFallback = null; - Func, Task> onFallbackAsync = r => { resultPassedToOnFallback = r.Result; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() + { + Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + bool onFallbackExecuted = false; + Func, Task> onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; - ResultClass resultFromDelegate = new ResultClass(ResultPrimitive.Fault); - await fallbackPolicy.ExecuteAsync(() => Task.FromResult(resultFromDelegate)); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - fallbackActionExecuted.Should().BeTrue(); - resultPassedToOnFallback.Should().NotBeNull(); - resultPassedToOnFallback.Should().Be(resultFromDelegate); - } + await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - [Fact] - public async Task Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() - { - Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); + onFallbackExecuted.Should().BeFalse(); + } - bool onFallbackExecuted = false; - Func, Task> onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; + #endregion - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + #region Context passing tests - await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + [Fact] + public void Should_call_onFallback_with_the_passed_context() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - onFallbackExecuted.Should().BeFalse(); - } + IDictionary contextData = null; - #endregion + Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - #region Context passing tests + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public void Should_call_onFallback_with_the_passed_context() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Result + .Should().Be(ResultPrimitive.Substitute); - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + IDictionary contextData = null; - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public async Task Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + (await fallbackPolicy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Result.Should().Be(ResultPrimitive.Substitute); - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + [Fact] + public void Should_call_onFallback_with_independent_context_for_independent_calls() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + IDictionary contextData = new Dictionary(); - (await fallbackPolicy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Result.Should().Be(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; return TaskHelper.EmptyTask; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), new { key = "value1" }.AsDictionary()) + .Result + .Should().Be(ResultPrimitive.Substitute); - IDictionary contextData = new Dictionary(); + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.FaultAgain), new { key = "value2" }.AsDictionary()) + .Result + .Should().Be(ResultPrimitive.Substitute); - Func, Context, Task> onFallbackAsync = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; return TaskHelper.EmptyTask; }; + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(ResultPrimitive.Fault); + contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); + contextData[ResultPrimitive.Fault].Should().Be("value1"); + contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction, onFallbackAsync); + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool onFallbackExecuted = false; - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), new { key = "value1" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.FaultAgain), new { key = "value2" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction, onFallbackAsync); - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(ResultPrimitive.Fault); - contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); - contextData[ResultPrimitive.Fault].Should().Be("value1"); - contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool onFallbackExecuted = false; + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - Func, Context, Task> onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction, onFallbackAsync); + Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public void Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Result + .Should().Be(ResultPrimitive.Substitute); - Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + [Fact] + public async Task Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public async Task Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); - Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + [Fact] + public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool fallbackExecuted = false; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + Func> fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return Task.FromResult(ResultPrimitive.Substitute); + }; + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - await fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrowAsync(); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); - [Fact] - public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool fallbackExecuted = false; - Func> fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return Task.FromResult(ResultPrimitive.Substitute); - }; - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } + #endregion - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + #region Fault passing tests - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); + [Fact] + public async Task Should_call_fallbackAction_with_the_fault() + { + DelegateResult fallbackOutcome = null; + Func, Context, CancellationToken, Task> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } - #endregion + Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; - #region Fault passing tests + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallback); - [Fact] - public async Task Should_call_fallbackAction_with_the_fault() - { - DelegateResult fallbackOutcome = null; + var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Fault)); + result.Should().Be(ResultPrimitive.Substitute); - Func, Context, CancellationToken, Task> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; + [Fact] + public async Task Should_call_fallbackAction_with_the_fault_when_execute_and_capture() + { + DelegateResult fallbackOutcome = null; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallback); + Func, Context, CancellationToken, Task> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; - var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Fault)); - result.Should().Be(ResultPrimitive.Substitute); + Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallback); - [Fact] - public async Task Should_call_fallbackAction_with_the_fault_when_execute_and_capture() - { - DelegateResult fallbackOutcome = null; + var result = await fallbackPolicy.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); + result.Should().NotBeNull(); + result.Result.Should().Be(ResultPrimitive.Substitute); - Func, Context, CancellationToken, Task> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; + [Fact] + public async Task Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() + { + DelegateResult fallbackOutcome = null; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallback); + Func, Context, CancellationToken, Task> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; - var result = await fallbackPolicy.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); - result.Should().NotBeNull(); - result.Result.Should().Be(ResultPrimitive.Substitute); + Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallback); - [Fact] - public async Task Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() - { - DelegateResult fallbackOutcome = null; + var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); + result.Should().Be(ResultPrimitive.FaultAgain); - Func, Context, CancellationToken, Task> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackOutcome.Should().BeNull(); + } - Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; + #endregion - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallback); - var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); - result.Should().Be(ResultPrimitive.FaultAgain); + #region Cancellation tests - fallbackOutcome.Should().BeNull(); - } + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - #endregion + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - #region Cancellation tests + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + AttemptDuringWhichToCancel = null, + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + AttemptDuringWhichToCancel = null, + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeTrue(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + cancellationTokenSource.Cancel(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .FallbackAsync(fallbackAction); - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .FallbackAsync(fallbackAction); + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeTrue(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain)) + .Should().Be(ResultPrimitive.FaultYetAgain); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + { + bool fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain)) - .Should().Be(ResultPrimitive.FaultYetAgain); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); - - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs b/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs index 897684c7edb..1d028e36851 100644 --- a/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs @@ -8,838 +8,837 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.Fallback +namespace Polly.Specs.Fallback; + +public class FallbackTResultSpecs { - public class FallbackTResultSpecs + #region Configuration guard condition tests + + [Fact] + public void Should_throw_when_fallback_action_is_null() { - #region Configuration guard condition tests + Func fallbackAction = null; - [Fact] - public void Should_throw_when_fallback_action_is_null() - { - Func fallbackAction = null; + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); + + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null() + { + Func fallbackAction = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null() - { - Func fallbackAction = null; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback() + { + Func fallbackAction = null; + Action> onFallback = _ => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback() - { - Func fallbackAction = null; - Action> onFallback = _ => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() + { + Func fallbackAction = null; + Action> onFallback = _ => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() - { - Func fallbackAction = null; - Action> onFallback = _ => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() + { + Func fallbackAction = null; + Action, Context> onFallback = (_, _) => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() - { - Func fallbackAction = null; - Action, Context> onFallback = (_, _) => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() + { + Func fallbackAction = null; + Action, Context> onFallback = (_, _) => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() - { - Func fallbackAction = null; - Action, Context> onFallback = (_, _) => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + Func fallbackAction = () => ResultPrimitive.Substitute; + Action> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - Func fallbackAction = () => ResultPrimitive.Substitute; - Action> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; + Action> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; - Action> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; + Action, Context> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; - Action, Context> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() + { + Func fallbackAction = (_, _) => ResultPrimitive.Substitute; + Action, Context> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() - { - Func fallbackAction = (_, _) => ResultPrimitive.Substitute; - Action, Context> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + #endregion - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + #region Policy operation tests - #endregion + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - #region Policy operation tests + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackPolicy.Execute(() => ResultPrimitive.Good); + + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.Execute(() => ResultPrimitive.Good); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.FaultAgain); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() + { + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(ResultPrimitive.Substitute); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.FaultAgain); + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); + } - fallbackActionExecuted.Should().BeFalse(); - } + [Fact] + public void Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - [Fact] - public void Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() - { - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(ResultPrimitive.Substitute); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); - [Fact] - public void Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.Substitute); - [Fact] - public void Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.Substitute); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(_ => false) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Fault); - [Fact] - public void Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(_ => false) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Fault); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(r => r == ResultPrimitive.Fault) + .OrResult(r => r == ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeFalse(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(r => r == ResultPrimitive.Fault) - .OrResult(r => r == ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_result_raised_matches_handling_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(_ => true) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); - [Fact] - public void Should_execute_fallback_when_result_raised_matches_handling_predicates() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeTrue(); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(_ => true) - .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); + [Fact] + public void Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + + FallbackPolicy fallbackPolicy = Policy + .HandleResult(_ => true) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() + [Fact] + public void Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted = true; + return new ResultClass(ResultPrimitive.Fault, "FromFallbackAction"); + }; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(_ => true) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); + fallbackPolicy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate")) + .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => - { - fallbackActionExecuted = true; - return new ResultClass(ResultPrimitive.Fault, "FromFallbackAction"); - }; + #endregion - FallbackPolicy fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Fallback(fallbackAction); + #region onPolicyEvent delegate tests - fallbackPolicy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate")) - .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); + [Fact] + public void Should_call_onFallback_passing_result_triggering_fallback() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return new ResultClass(ResultPrimitive.Substitute); }; - fallbackActionExecuted.Should().BeTrue(); - } + ResultClass resultPassedToOnFallback = null; + Action> onFallback = r => { resultPassedToOnFallback = r.Result; }; - #endregion + FallbackPolicy fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - #region onPolicyEvent delegate tests + ResultClass resultFromDelegate = new ResultClass(ResultPrimitive.Fault); + fallbackPolicy.Execute(() => resultFromDelegate); - [Fact] - public void Should_call_onFallback_passing_result_triggering_fallback() - { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return new ResultClass(ResultPrimitive.Substitute); }; + fallbackActionExecuted.Should().BeTrue(); + resultPassedToOnFallback.Should().NotBeNull(); + resultPassedToOnFallback.Should().Be(resultFromDelegate); + } - ResultClass resultPassedToOnFallback = null; - Action> onFallback = r => { resultPassedToOnFallback = r.Result; }; + [Fact] + public void Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() + { + Func fallbackAction = () => ResultPrimitive.Substitute; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + bool onFallbackExecuted = false; + Action> onFallback = _ => { onFallbackExecuted = true; }; - ResultClass resultFromDelegate = new ResultClass(ResultPrimitive.Fault); - fallbackPolicy.Execute(() => resultFromDelegate); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - fallbackActionExecuted.Should().BeTrue(); - resultPassedToOnFallback.Should().NotBeNull(); - resultPassedToOnFallback.Should().Be(resultFromDelegate); - } + fallbackPolicy.Execute(() => ResultPrimitive.Good); - [Fact] - public void Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() - { - Func fallbackAction = () => ResultPrimitive.Substitute; + onFallbackExecuted.Should().BeFalse(); + } - bool onFallbackExecuted = false; - Action> onFallback = _ => { onFallbackExecuted = true; }; + #endregion - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + #region Context passing tests - fallbackPolicy.Execute(() => ResultPrimitive.Good); + [Fact] + public void Should_call_onFallback_with_the_passed_context() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; - onFallbackExecuted.Should().BeFalse(); - } + IDictionary contextData = null; - #endregion + Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; - #region Context passing tests + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_onFallback_with_the_passed_context() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; + fallbackPolicy.Execute(_ => ResultPrimitive.Fault, + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Should().Be(ResultPrimitive.Substitute); - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; + [Fact] + public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + IDictionary contextData = null; - fallbackPolicy.Execute(_ => ResultPrimitive.Fault, - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; + fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Result.Should().Be(ResultPrimitive.Substitute); - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; + [Fact] + public void Should_call_onFallback_with_independent_context_for_independent_calls() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + IDictionary contextData = new Dictionary(); - fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result.Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; + fallbackPolicy.Execute(_ => ResultPrimitive.Fault, new { key = "value1" }.AsDictionary()) + .Should().Be(ResultPrimitive.Substitute); - IDictionary contextData = new Dictionary(); + fallbackPolicy.Execute(_ => ResultPrimitive.FaultAgain, new { key = "value2" }.AsDictionary()) + .Should().Be(ResultPrimitive.Substitute); - Action, Context> onFallback = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; }; + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(ResultPrimitive.Fault); + contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); + contextData[ResultPrimitive.Fault].Should().Be("value1"); + contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); + } - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool onFallbackExecuted = false; - fallbackPolicy.Execute(_ => ResultPrimitive.Fault, new { key = "value1" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + Func fallbackAction = _ => ResultPrimitive.Substitute; + Action, Context> onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; - fallbackPolicy.Execute(_ => ResultPrimitive.FaultAgain, new { key = "value2" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction, onFallback); - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(ResultPrimitive.Fault); - contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); - contextData[ResultPrimitive.Fault].Should().Be("value1"); - contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool onFallbackExecuted = false; + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - Func fallbackAction = _ => ResultPrimitive.Substitute; - Action, Context> onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction, onFallback); + Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); + Action, Context> onFallback = (_, _) => { }; - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + fallbackPolicy.Execute(_ => ResultPrimitive.Fault, + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Should().Be(ResultPrimitive.Substitute); - Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action, Context> onFallback = (_, _) => { }; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; - fallbackPolicy.Execute(_ => ResultPrimitive.Fault, - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (_, _) => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Result.Should().Be(ResultPrimitive.Substitute); - Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action, Context> onFallback = (_, _) => { }; + [Fact] + public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + bool fallbackExecuted = false; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + Func fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return ResultPrimitive.Substitute; }; - fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result.Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (_, _) => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - bool fallbackExecuted = false; + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); - Func fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return ResultPrimitive.Substitute; }; + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } + #endregion - Action, Context> onFallback = (_, _) => { }; + #region Fault passing tests - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_call_fallbackAction_with_the_fault() + { + DelegateResult fallbackOutcome = null; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); + Func, Context, CancellationToken, ResultPrimitive> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } - #endregion + Action, Context> onFallback = (_, _) => { }; - #region Fault passing tests + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_fault() - { - DelegateResult fallbackOutcome = null; + fallbackPolicy.Execute(() => ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Substitute); - Func, Context, CancellationToken, ResultPrimitive> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - Action, Context> onFallback = (_, _) => { }; + [Fact] + public void Should_call_fallbackAction_with_the_fault_when_execute_and_capture() + { + DelegateResult fallbackOutcome = null; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + Func, Context, CancellationToken, ResultPrimitive> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; - fallbackPolicy.Execute(() => ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (_, _) => { }; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_fault_when_execute_and_capture() - { - DelegateResult fallbackOutcome = null; + var result = fallbackPolicy.ExecuteAndCapture(() => ResultPrimitive.Fault); + result.Should().NotBeNull(); + result.Result.Should().Be(ResultPrimitive.Substitute); - Func, Context, CancellationToken, ResultPrimitive> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - Action, Context> onFallback = (_, _) => { }; + [Fact] + public void Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() + { + DelegateResult fallbackOutcome = null; - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + Func, Context, CancellationToken, ResultPrimitive> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; - var result = fallbackPolicy.ExecuteAndCapture(() => ResultPrimitive.Fault); - result.Should().NotBeNull(); - result.Result.Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (_, _) => { }; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + FallbackPolicy fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() - { - DelegateResult fallbackOutcome = null; + fallbackPolicy.Execute(() => ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); - Func, Context, CancellationToken, ResultPrimitive> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; + fallbackOutcome.Should().BeNull(); + } - Action, Context> onFallback = (_, _) => { }; + #endregion - FallbackPolicy fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + #region Cancellation tests - fallbackPolicy.Execute(() => ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackOutcome.Should().BeNull(); - } + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - #endregion + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - #region Cancellation tests + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + AttemptDuringWhichToCancel = null, + }; - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + AttemptDuringWhichToCancel = null, + }; - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeTrue(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + cancellationTokenSource.Cancel(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Fallback(fallbackAction); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeTrue(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeTrue(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain) + .Should().Be(ResultPrimitive.FaultYetAgain); + attemptsInvoked.Should().Be(1); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + fallbackActionExecuted.Should().BeFalse(); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + { + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain) - .Should().Be(ResultPrimitive.FaultYetAgain); - attemptsInvoked.Should().Be(1); + FallbackPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - bool fallbackActionExecuted = false; - Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - FallbackPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); - - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs b/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs index 566053f9373..e71f35972c9 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs @@ -4,60 +4,59 @@ using System.Threading; using Xunit.Abstractions; -namespace Polly.Specs.Helpers.Bulkhead +namespace Polly.Specs.Helpers.Bulkhead; + +public class AnnotatedOutputHelper : ITestOutputHelper { - public class AnnotatedOutputHelper : ITestOutputHelper + private class Item { - private class Item + private static int monotonicSequence; + + public Item(string format, object[] args) { - private static int monotonicSequence; + TimeStamp = DateTimeOffset.UtcNow; + Position = Interlocked.Increment(ref monotonicSequence); - public Item(string format, object[] args) - { - TimeStamp = DateTimeOffset.UtcNow; - Position = Interlocked.Increment(ref monotonicSequence); + Format = format; + Args = args; + } - Format = format; - Args = args; - } + public int Position { get; } + public DateTimeOffset TimeStamp { get; } + public string Format { get; } + public object[] Args { get; } + } - public int Position { get; } - public DateTimeOffset TimeStamp { get; } - public string Format { get; } - public object[] Args { get; } - } + private readonly ConcurrentDictionary items = new ConcurrentDictionary(); - private readonly ConcurrentDictionary items = new ConcurrentDictionary(); + private readonly object[] noArgs = Array.Empty(); - private readonly object[] noArgs = Array.Empty(); + private readonly ITestOutputHelper innerOutputHelper; - private readonly ITestOutputHelper innerOutputHelper; + public AnnotatedOutputHelper(ITestOutputHelper innerOutputHelper) + { + this.innerOutputHelper = innerOutputHelper ?? throw new ArgumentNullException(nameof(innerOutputHelper)); + } - public AnnotatedOutputHelper(ITestOutputHelper innerOutputHelper) + public void Flush() + { + // Some IDEs limit the number of lines of output displayed in a test result. Display the lines in reverse order so that we always see the most recent. + var toOutput = items.Select(kvp => kvp.Value).OrderBy(i => i.Position).Reverse(); + foreach (var item in toOutput) { - this.innerOutputHelper = innerOutputHelper ?? throw new ArgumentNullException(nameof(innerOutputHelper)); + innerOutputHelper.WriteLine(item.TimeStamp.ToString("o") + ": " + item.Format, item.Args); } - public void Flush() - { - // Some IDEs limit the number of lines of output displayed in a test result. Display the lines in reverse order so that we always see the most recent. - var toOutput = items.Select(kvp => kvp.Value).OrderBy(i => i.Position).Reverse(); - foreach (var item in toOutput) - { - innerOutputHelper.WriteLine(item.TimeStamp.ToString("o") + ": " + item.Format, item.Args); - } - - items.Clear(); - } + items.Clear(); + } - public void WriteLine(string message) - { - items.TryAdd(Guid.NewGuid(), new Item(message ?? string.Empty, noArgs)); - } + public void WriteLine(string message) + { + items.TryAdd(Guid.NewGuid(), new Item(message ?? string.Empty, noArgs)); + } - public void WriteLine(string format, params object[] args) - { - items.TryAdd(Guid.NewGuid(), new Item(format ?? string.Empty, args == null || args.Length == 0 ? noArgs : args)); - } + public void WriteLine(string format, params object[] args) + { + items.TryAdd(Guid.NewGuid(), new Item(format ?? string.Empty, args == null || args.Length == 0 ? noArgs : args)); } } diff --git a/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs b/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs index 3a46bceba25..be90eee9198 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs @@ -1,20 +1,19 @@ using System; -namespace Polly.Specs.Helpers.Bulkhead +namespace Polly.Specs.Helpers.Bulkhead; + +public class AssertionFailure { - public class AssertionFailure + public AssertionFailure(int expected, int actual, string measure) { - public AssertionFailure(int expected, int actual, string measure) - { - if (string.IsNullOrWhiteSpace(measure)) throw new ArgumentNullException(nameof(measure)); - - Expected = expected; - Actual = actual; - Measure = measure; - } + if (string.IsNullOrWhiteSpace(measure)) throw new ArgumentNullException(nameof(measure)); - public int Expected { get; } - public int Actual { get; } - public string Measure { get; } + Expected = expected; + Actual = actual; + Measure = measure; } + + public int Expected { get; } + public int Actual { get; } + public string Measure { get; } } diff --git a/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs b/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs index ce84758bdb2..5417f0d1f72 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs @@ -1,17 +1,16 @@ using Xunit.Abstractions; -namespace Polly.Specs.Helpers.Bulkhead +namespace Polly.Specs.Helpers.Bulkhead; + +public class SilentOutputHelper : ITestOutputHelper { - public class SilentOutputHelper : ITestOutputHelper + public void WriteLine(string message) { - public void WriteLine(string message) - { - // Do nothing: intentionally silent. - } + // Do nothing: intentionally silent. + } - public void WriteLine(string format, params object[] args) - { - // Do nothing: intentionally silent. - } + public void WriteLine(string format, params object[] args) + { + // Do nothing: intentionally silent. } } diff --git a/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs b/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs index 97372ff0ad1..42a6eebf9cb 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs @@ -4,77 +4,156 @@ using Polly.Bulkhead; using Xunit.Abstractions; -namespace Polly.Specs.Helpers.Bulkhead +namespace Polly.Specs.Helpers.Bulkhead; + +/// +/// A traceable action that can be executed on a , to support specs. +/// We can execute multiple instances of in parallel on a bulkhead, and manually control the cancellation and completion of each, to provide determinate tests on the bulkhead operation. The status of this as it executes is fully traceable through the property. +/// +public class TraceableAction : IDisposable { - /// - /// A traceable action that can be executed on a , to support specs. - /// We can execute multiple instances of in parallel on a bulkhead, and manually control the cancellation and completion of each, to provide determinate tests on the bulkhead operation. The status of this as it executes is fully traceable through the property. - /// - public class TraceableAction : IDisposable - { - private readonly string _id; - private readonly ITestOutputHelper _testOutputHelper; + private readonly string _id; + private readonly ITestOutputHelper _testOutputHelper; - private readonly TaskCompletionSource _tcsProxyForRealWork = new TaskCompletionSource(); - private readonly CancellationTokenSource CancellationSource = new CancellationTokenSource(); + private readonly TaskCompletionSource _tcsProxyForRealWork = new TaskCompletionSource(); + private readonly CancellationTokenSource CancellationSource = new CancellationTokenSource(); - private TraceableActionStatus _status; - private readonly AutoResetEvent _statusChanged; + private TraceableActionStatus _status; + private readonly AutoResetEvent _statusChanged; - public TraceableActionStatus Status + public TraceableActionStatus Status + { + get { return _status; } + set { - get { return _status; } - set - { - _status = value; - _testOutputHelper.WriteLine(_id + "Updated status to {0}, signalling AutoResetEvent.", _status); - SignalStateChange(); - } + _status = value; + _testOutputHelper.WriteLine(_id + "Updated status to {0}, signalling AutoResetEvent.", _status); + SignalStateChange(); } + } - public TraceableAction(int id, AutoResetEvent statusChanged, ITestOutputHelper testOutputHelper) - { - _id = $"{id:00}: "; - _statusChanged = statusChanged; - _testOutputHelper = testOutputHelper; - } + public TraceableAction(int id, AutoResetEvent statusChanged, ITestOutputHelper testOutputHelper) + { + _id = $"{id:00}: "; + _statusChanged = statusChanged; + _testOutputHelper = testOutputHelper; + } - public void SignalStateChange() - { - _testOutputHelper.WriteLine("--signalled--"); - _statusChanged.Set(); - } + public void SignalStateChange() + { + _testOutputHelper.WriteLine("--signalled--"); + _statusChanged.Set(); + } - public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) - { - return ExecuteThroughSyncBulkheadOuter( - () => bulkhead.Execute(_ => ExecuteThroughSyncBulkheadInner(), CancellationSource.Token) - ); - } + public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) + { + return ExecuteThroughSyncBulkheadOuter( + () => bulkhead.Execute(_ => ExecuteThroughSyncBulkheadInner(), CancellationSource.Token) + ); + } + + public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) + { + return ExecuteThroughSyncBulkheadOuter( + () => bulkhead.Execute(_ => { ExecuteThroughSyncBulkheadInner(); return default; }, CancellationSource.Token) + ); + } - public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) + // Note re TaskCreationOptions.LongRunning: Testing the parallelization of the bulkhead policy efficiently requires the ability to start large numbers of parallel tasks in a short space of time. The ThreadPool's algorithm of only injecting extra threads (when necessary) at a rate of two-per-second however makes high-volume tests using the ThreadPool both slow and flaky. For PCL tests further, ThreadPool.SetMinThreads(...) is not available, to mitigate this. Using TaskCreationOptions.LongRunning allows us to force tasks to be started near-instantly on non-ThreadPool threads. + private Task ExecuteThroughSyncBulkheadOuter(Action executeThroughBulkheadInner) + { + if (Status != TraceableActionStatus.Unstarted) { - return ExecuteThroughSyncBulkheadOuter( - () => bulkhead.Execute(_ => { ExecuteThroughSyncBulkheadInner(); return default; }, CancellationSource.Token) - ); + throw new InvalidOperationException(_id + "Action has previously been started."); } + Status = TraceableActionStatus.StartRequested; - // Note re TaskCreationOptions.LongRunning: Testing the parallelization of the bulkhead policy efficiently requires the ability to start large numbers of parallel tasks in a short space of time. The ThreadPool's algorithm of only injecting extra threads (when necessary) at a rate of two-per-second however makes high-volume tests using the ThreadPool both slow and flaky. For PCL tests further, ThreadPool.SetMinThreads(...) is not available, to mitigate this. Using TaskCreationOptions.LongRunning allows us to force tasks to be started near-instantly on non-ThreadPool threads. - private Task ExecuteThroughSyncBulkheadOuter(Action executeThroughBulkheadInner) + return Task.Factory.StartNew(() => { - if (Status != TraceableActionStatus.Unstarted) + try + { + Status = TraceableActionStatus.QueueingForSemaphore; + + executeThroughBulkheadInner(); + } + catch (BulkheadRejectedException) { - throw new InvalidOperationException(_id + "Action has previously been started."); + Status = TraceableActionStatus.Rejected; } - Status = TraceableActionStatus.StartRequested; + catch (OperationCanceledException) + { + if (Status != TraceableActionStatus.Canceled) + { + _testOutputHelper.WriteLine(_id + "Caught queue cancellation."); + Status = TraceableActionStatus.Canceled; + } // else: was execution cancellation rethrown: ignore + } + catch (AggregateException ae) + { + if (ae.InnerExceptions.Count == 1 && ae.InnerException is OperationCanceledException) + { + if (Status != TraceableActionStatus.Canceled) + { + _testOutputHelper.WriteLine(_id + "Caught queue cancellation."); + Status = TraceableActionStatus.Canceled; + } // else: was execution cancellation rethrown: ignore + } + else throw; + } + catch (Exception e) + { + _testOutputHelper.WriteLine(_id + "Caught unexpected exception during execution: " + e); + + Status = TraceableActionStatus.Faulted; + } + finally + { + // Exiting the execution successfully is also a change of state (on which assertions may be occurring) in that it releases a semaphore slot sucessfully. + // There can also be races between assertions and executions-responding-to-previous-state-changes, so a second signal presents another opportunity for assertions to be run. + SignalStateChange(); + } + }, + TaskCreationOptions.LongRunning); + } + + private void ExecuteThroughSyncBulkheadInner() + { + Status = TraceableActionStatus.Executing; + + _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously).Wait(); + + _testOutputHelper.WriteLine(_id + "Exiting execution."); + } + + public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) + { + return ExecuteThroughAsyncBulkheadOuter( + () => bulkhead.ExecuteAsync(async _ => await ExecuteThroughAsyncBulkheadInner(), CancellationSource.Token) + ); + } + + public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) + { + return ExecuteThroughAsyncBulkheadOuter( + () => bulkhead.ExecuteAsync(async _ => { await ExecuteThroughAsyncBulkheadInner(); return default; }, CancellationSource.Token) + ); + } - return Task.Factory.StartNew(() => + public Task ExecuteThroughAsyncBulkheadOuter(Func executeThroughBulkheadInner) + { + if (Status != TraceableActionStatus.Unstarted) + { + throw new InvalidOperationException(_id + "Action has previously been started."); + } + Status = TraceableActionStatus.StartRequested; + + return Task.Factory.StartNew(async () => { try { Status = TraceableActionStatus.QueueingForSemaphore; - executeThroughBulkheadInner(); + await executeThroughBulkheadInner(); } catch (BulkheadRejectedException) { @@ -86,19 +165,7 @@ private Task ExecuteThroughSyncBulkheadOuter(Action executeThroughBulkheadInner) { _testOutputHelper.WriteLine(_id + "Caught queue cancellation."); Status = TraceableActionStatus.Canceled; - } // else: was execution cancellation rethrown: ignore - } - catch (AggregateException ae) - { - if (ae.InnerExceptions.Count == 1 && ae.InnerException is OperationCanceledException) - { - if (Status != TraceableActionStatus.Canceled) - { - _testOutputHelper.WriteLine(_id + "Caught queue cancellation."); - Status = TraceableActionStatus.Canceled; - } // else: was execution cancellation rethrown: ignore - } - else throw; + } // else: was execution cancellation rethrown: ignore } catch (Exception e) { @@ -113,126 +180,58 @@ private Task ExecuteThroughSyncBulkheadOuter(Action executeThroughBulkheadInner) SignalStateChange(); } }, - TaskCreationOptions.LongRunning); - } + TaskCreationOptions.LongRunning).Unwrap(); + } - private void ExecuteThroughSyncBulkheadInner() - { - Status = TraceableActionStatus.Executing; + private async Task ExecuteThroughAsyncBulkheadInner() + { + Status = TraceableActionStatus.Executing; - _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously).Wait(); + await _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously); - _testOutputHelper.WriteLine(_id + "Exiting execution."); - } + _testOutputHelper.WriteLine(_id + "Exiting execution."); + } - public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) + private Action> CaptureCompletion() => t => + { + if (t.IsCanceled) { - return ExecuteThroughAsyncBulkheadOuter( - () => bulkhead.ExecuteAsync(async _ => await ExecuteThroughAsyncBulkheadInner(), CancellationSource.Token) - ); - } + _testOutputHelper.WriteLine(_id + "Cancelling execution."); - public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) - { - return ExecuteThroughAsyncBulkheadOuter( - () => bulkhead.ExecuteAsync(async _ => { await ExecuteThroughAsyncBulkheadInner(); return default; }, CancellationSource.Token) - ); + Status = TraceableActionStatus.Canceled; + throw new OperationCanceledException(CancellationSource.Token); // Exception rethrown for the purpose of testing exceptions thrown through the BulkheadEngine. } - - public Task ExecuteThroughAsyncBulkheadOuter(Func executeThroughBulkheadInner) + else if (t.IsFaulted) { - if (Status != TraceableActionStatus.Unstarted) - { - throw new InvalidOperationException(_id + "Action has previously been started."); - } - Status = TraceableActionStatus.StartRequested; - - return Task.Factory.StartNew(async () => - { - try - { - Status = TraceableActionStatus.QueueingForSemaphore; - - await executeThroughBulkheadInner(); - } - catch (BulkheadRejectedException) - { - Status = TraceableActionStatus.Rejected; - } - catch (OperationCanceledException) - { - if (Status != TraceableActionStatus.Canceled) - { - _testOutputHelper.WriteLine(_id + "Caught queue cancellation."); - Status = TraceableActionStatus.Canceled; - } // else: was execution cancellation rethrown: ignore - } - catch (Exception e) - { - _testOutputHelper.WriteLine(_id + "Caught unexpected exception during execution: " + e); + _testOutputHelper.WriteLine(_id + "Execution faulted."); + if (t.Exception != null) { _testOutputHelper.WriteLine(_id + "Exception: " + t.Exception); } - Status = TraceableActionStatus.Faulted; - } - finally - { - // Exiting the execution successfully is also a change of state (on which assertions may be occurring) in that it releases a semaphore slot sucessfully. - // There can also be races between assertions and executions-responding-to-previous-state-changes, so a second signal presents another opportunity for assertions to be run. - SignalStateChange(); - } - }, - TaskCreationOptions.LongRunning).Unwrap(); + Status = TraceableActionStatus.Faulted; } - - private async Task ExecuteThroughAsyncBulkheadInner() + else { - Status = TraceableActionStatus.Executing; + _testOutputHelper.WriteLine(_id + "Completing execution."); - await _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously); - - _testOutputHelper.WriteLine(_id + "Exiting execution."); + Status = TraceableActionStatus.Completed; } - private Action> CaptureCompletion() => t => - { - if (t.IsCanceled) - { - _testOutputHelper.WriteLine(_id + "Cancelling execution."); - - Status = TraceableActionStatus.Canceled; - throw new OperationCanceledException(CancellationSource.Token); // Exception rethrown for the purpose of testing exceptions thrown through the BulkheadEngine. - } - else if (t.IsFaulted) - { - _testOutputHelper.WriteLine(_id + "Execution faulted."); - if (t.Exception != null) { _testOutputHelper.WriteLine(_id + "Exception: " + t.Exception); } - - Status = TraceableActionStatus.Faulted; - } - else - { - _testOutputHelper.WriteLine(_id + "Completing execution."); - - Status = TraceableActionStatus.Completed; - } - - }; + }; - public void AllowCompletion() - { - _tcsProxyForRealWork.SetResult(null); - } + public void AllowCompletion() + { + _tcsProxyForRealWork.SetResult(null); + } - public void Cancel() - { - if (CancellationSource.IsCancellationRequested) { throw new InvalidOperationException(_id + "Action has already been cancelled."); } - CancellationSource.Cancel(); + public void Cancel() + { + if (CancellationSource.IsCancellationRequested) { throw new InvalidOperationException(_id + "Action has already been cancelled."); } + CancellationSource.Cancel(); - _tcsProxyForRealWork.SetCanceled(); - } + _tcsProxyForRealWork.SetCanceled(); + } - public void Dispose() - { - CancellationSource.Dispose(); - } + public void Dispose() + { + CancellationSource.Dispose(); } } diff --git a/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs b/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs index 6b8153bbf68..71cf17b4a89 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs @@ -1,17 +1,16 @@ -namespace Polly.Specs.Helpers.Bulkhead +namespace Polly.Specs.Helpers.Bulkhead; + +/// +/// States of a that can be tracked during testing. +/// +public enum TraceableActionStatus { - /// - /// States of a that can be tracked during testing. - /// - public enum TraceableActionStatus - { - Unstarted, - StartRequested, - QueueingForSemaphore, - Executing, - Rejected, - Canceled, - Faulted, - Completed, - } -} \ No newline at end of file + Unstarted, + StartRequested, + QueueingForSemaphore, + Executing, + Rejected, + Canceled, + Faulted, + Completed, +} diff --git a/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs b/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs index 6941c23bf73..4203a3c46c6 100644 --- a/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs +++ b/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs @@ -1,23 +1,22 @@ using System; using Polly.Caching; -namespace Polly.Specs.Helpers.Caching +namespace Polly.Specs.Helpers.Caching; + +/// +/// A configurable stub ICacheKeyStrategy, to support tests.. +/// +internal class StubCacheKeyStrategy : ICacheKeyStrategy { - /// - /// A configurable stub ICacheKeyStrategy, to support tests.. - /// - internal class StubCacheKeyStrategy : ICacheKeyStrategy - { - private readonly Func strategy; + private readonly Func strategy; - public StubCacheKeyStrategy(Func strategy) - { - this.strategy = strategy; - } + public StubCacheKeyStrategy(Func strategy) + { + this.strategy = strategy; + } - public string GetCacheKey(Context context) - { - return strategy(context); - } + public string GetCacheKey(Context context) + { + return strategy(context); } } diff --git a/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs b/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs index 4f1af11cc8e..406cd95f8e8 100644 --- a/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs +++ b/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs @@ -5,63 +5,62 @@ using Polly.Caching; using Polly.Utilities; -namespace Polly.Specs.Helpers.Caching +namespace Polly.Specs.Helpers.Caching; + +/// +/// An intentionally naive stub cache implementation. Its purpose is to be the simplest thing possible to support tests of the CachePolicy and CacheEngine, not a production-usable implementation. +/// +internal class StubCacheProvider : ISyncCacheProvider, IAsyncCacheProvider { - /// - /// An intentionally naive stub cache implementation. Its purpose is to be the simplest thing possible to support tests of the CachePolicy and CacheEngine, not a production-usable implementation. - /// - internal class StubCacheProvider : ISyncCacheProvider, IAsyncCacheProvider + class CacheItem { - class CacheItem + public CacheItem(object value, Ttl ttl) { - public CacheItem(object value, Ttl ttl) - { - Expiry = DateTimeOffset.MaxValue - SystemClock.DateTimeOffsetUtcNow() > ttl.Timespan ? SystemClock.DateTimeOffsetUtcNow().Add(ttl.Timespan) : DateTimeOffset.MaxValue; - Value = value; - } - - public readonly DateTimeOffset Expiry; - public readonly object Value; + Expiry = DateTimeOffset.MaxValue - SystemClock.DateTimeOffsetUtcNow() > ttl.Timespan ? SystemClock.DateTimeOffsetUtcNow().Add(ttl.Timespan) : DateTimeOffset.MaxValue; + Value = value; } - private readonly Dictionary cachedValues = new Dictionary(); + public readonly DateTimeOffset Expiry; + public readonly object Value; + } + + private readonly Dictionary cachedValues = new Dictionary(); - public (bool, object) TryGet(string key) + public (bool, object) TryGet(string key) + { + if (cachedValues.ContainsKey(key)) { - if (cachedValues.ContainsKey(key)) + if (SystemClock.DateTimeOffsetUtcNow() < cachedValues[key].Expiry) + { + return (true, cachedValues[key].Value); + } + else { - if (SystemClock.DateTimeOffsetUtcNow() < cachedValues[key].Expiry) - { - return (true, cachedValues[key].Value); - } - else - { - cachedValues.Remove(key); - } + cachedValues.Remove(key); } - return (false, null); } + return (false, null); + } - public void Put(string key, object value, Ttl ttl) - { - cachedValues[key] = new CacheItem(value, ttl); - } + public void Put(string key, object value, Ttl ttl) + { + cachedValues[key] = new CacheItem(value, ttl); + } - #region Naive async-over-sync implementation + #region Naive async-over-sync implementation - // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return Task.FromResult(TryGet(key)); - } + // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } - public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - Put(key, value, ttl); - return TaskHelper.EmptyTask; - } + public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + Put(key, value, ttl); + return TaskHelper.EmptyTask; + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs b/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs index 24da3019c21..a2f563e185f 100644 --- a/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs +++ b/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs @@ -4,48 +4,47 @@ using Polly.Caching; using Polly.Utilities; -namespace Polly.Specs.Helpers.Caching +namespace Polly.Specs.Helpers.Caching; + +internal class StubErroringCacheProvider : ISyncCacheProvider, IAsyncCacheProvider { - internal class StubErroringCacheProvider : ISyncCacheProvider, IAsyncCacheProvider - { - private Exception _getException; - private Exception _putException; + private Exception _getException; + private Exception _putException; - private StubCacheProvider innerProvider = new StubCacheProvider(); + private StubCacheProvider innerProvider = new StubCacheProvider(); - public StubErroringCacheProvider(Exception getException, Exception putException) - { - _getException = getException; - _putException = putException; - } + public StubErroringCacheProvider(Exception getException, Exception putException) + { + _getException = getException; + _putException = putException; + } - public (bool, object) TryGet(string key) - { - if (_getException != null) throw _getException; - return innerProvider.TryGet(key); - } + public (bool, object) TryGet(string key) + { + if (_getException != null) throw _getException; + return innerProvider.TryGet(key); + } - public void Put(string key, object value, Ttl ttl) - { - if (_putException != null) throw _putException; - innerProvider.Put(key, value, ttl); - } + public void Put(string key, object value, Ttl ttl) + { + if (_putException != null) throw _putException; + innerProvider.Put(key, value, ttl); + } - #region Naive async-over-sync implementation + #region Naive async-over-sync implementation - // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return Task.FromResult(TryGet(key)); - } + // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } - public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - Put(key, value, ttl); - return TaskHelper.EmptyTask; - } + public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + Put(key, value, ttl); + return TaskHelper.EmptyTask; + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Helpers/Caching/StubSerialized.cs b/src/Polly.Specs/Helpers/Caching/StubSerialized.cs index 8266865f3f5..dac764a54c4 100644 --- a/src/Polly.Specs/Helpers/Caching/StubSerialized.cs +++ b/src/Polly.Specs/Helpers/Caching/StubSerialized.cs @@ -1,24 +1,23 @@ -namespace Polly.Specs.Helpers.Caching -{ - /// - /// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! - /// - /// The type of the item being 'serialized'. - internal class StubSerialized - { - public TOriginal Original; +namespace Polly.Specs.Helpers.Caching; - public StubSerialized(TOriginal original) - { - Original = original; - } - } +/// +/// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! +/// +/// The type of the item being 'serialized'. +internal class StubSerialized +{ + public TOriginal Original; - /// - /// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! - /// - internal class StubSerialized : StubSerialized + public StubSerialized(TOriginal original) { - public StubSerialized(object obj) : base(obj) { } + Original = original; } -} \ No newline at end of file +} + +/// +/// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! +/// +internal class StubSerialized : StubSerialized +{ + public StubSerialized(object obj) : base(obj) { } +} diff --git a/src/Polly.Specs/Helpers/Caching/StubSerializer.cs b/src/Polly.Specs/Helpers/Caching/StubSerializer.cs index 7c5414c8f17..ad81ebab152 100644 --- a/src/Polly.Specs/Helpers/Caching/StubSerializer.cs +++ b/src/Polly.Specs/Helpers/Caching/StubSerializer.cs @@ -1,25 +1,24 @@ using System; using Polly.Caching; -namespace Polly.Specs.Helpers.Caching -{ - /// - /// A configurable stub serializer implementation to support tests around serializing cache providers. - /// - /// The type of the results being cached. - /// The type of the serialized values. - internal class StubSerializer : ICacheItemSerializer - { - private readonly Func _serialize; - private readonly Func _deserialize; +namespace Polly.Specs.Helpers.Caching; - public StubSerializer(Func serialize, Func deserialize) - { - _serialize = serialize; - _deserialize = deserialize; - } - public TSerialized Serialize(TResult objectToSerialize) => _serialize(objectToSerialize); +/// +/// A configurable stub serializer implementation to support tests around serializing cache providers. +/// +/// The type of the results being cached. +/// The type of the serialized values. +internal class StubSerializer : ICacheItemSerializer +{ + private readonly Func _serialize; + private readonly Func _deserialize; - public TResult Deserialize(TSerialized objectToDeserialize) => _deserialize(objectToDeserialize); + public StubSerializer(Func serialize, Func deserialize) + { + _serialize = serialize; + _deserialize = deserialize; } + public TSerialized Serialize(TResult objectToSerialize) => _serialize(objectToSerialize); + + public TResult Deserialize(TSerialized objectToDeserialize) => _deserialize(objectToDeserialize); } diff --git a/src/Polly.Specs/Helpers/Constants.cs b/src/Polly.Specs/Helpers/Constants.cs index c2b7f2816c5..27779b1fcba 100644 --- a/src/Polly.Specs/Helpers/Constants.cs +++ b/src/Polly.Specs/Helpers/Constants.cs @@ -1,18 +1,17 @@ -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +/// +/// Constants supporting tests. +/// +public class Constants { /// - /// Constants supporting tests. + /// Denotes a test collection dependent on manipulating the abstracted . These tests are not parallelized. /// - public class Constants - { - /// - /// Denotes a test collection dependent on manipulating the abstracted . These tests are not parallelized. - /// - public const string SystemClockDependentTestCollection = "SystemClockDependentTestCollection"; + public const string SystemClockDependentTestCollection = "SystemClockDependentTestCollection"; - /// - /// Denotes a test collection making heavy use of parallel threads. These tests are not run in parallel with each other, to reduce heavy use of threads in the build/CI environment. - /// - public const string ParallelThreadDependentTestCollection = "ParallelThreadDependentTestCollection"; - } -} \ No newline at end of file + /// + /// Denotes a test collection making heavy use of parallel threads. These tests are not run in parallel with each other, to reduce heavy use of threads in the build/CI environment. + /// + public const string ParallelThreadDependentTestCollection = "ParallelThreadDependentTestCollection"; +} diff --git a/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs b/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs index 269a57d27b5..8288594e11d 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs @@ -1,39 +1,38 @@ using System; using System.Collections.Generic; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class ContextualPolicyExtensions { - public static class ContextualPolicyExtensions + public static void RaiseException(this Policy policy, + int numberOfTimesToRaiseException, + IDictionary contextData, + Action configureException = null) where TException : Exception, new() { - public static void RaiseException(this Policy policy, - int numberOfTimesToRaiseException, - IDictionary contextData, - Action configureException = null) where TException : Exception, new() - { - int counter = 0; + int counter = 0; - policy.Execute(_ => + policy.Execute(_ => + { + if (counter < numberOfTimesToRaiseException) { - if (counter < numberOfTimesToRaiseException) - { - counter++; - - var exception = new TException(); + counter++; - configureException?.Invoke(exception, counter); + var exception = new TException(); - throw exception; - } - }, contextData); - } + configureException?.Invoke(exception, counter); - public static void RaiseException( - this Policy policy, - IDictionary contextData, - Action configureException = null) where TException : Exception, new() - { - policy.RaiseException(1, contextData, configureException); - } + throw exception; + } + }, contextData); + } + public static void RaiseException( + this Policy policy, + IDictionary contextData, + Action configureException = null) where TException : Exception, new() + { + policy.RaiseException(1, contextData, configureException); } -} \ No newline at end of file + +} diff --git a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs index a5b91ec81e4..91fa7302034 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs @@ -4,35 +4,34 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class ContextualPolicyExtensionsAsync { - public static class ContextualPolicyExtensionsAsync + + public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() { + int counter = 0; - public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() + return policy.ExecuteAsync((_, _) => { - int counter = 0; - - return policy.ExecuteAsync((_, _) => + if (counter < numberOfTimesToRaiseException) { - if (counter < numberOfTimesToRaiseException) - { - counter++; - - var exception = new TException(); + counter++; - configureException?.Invoke(exception, counter); + var exception = new TException(); - throw exception; - } - return TaskHelper.EmptyTask; - }, contextData, cancellationToken); - } + configureException?.Invoke(exception, counter); - public static Task RaiseExceptionAsync(this AsyncPolicy policy, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() - { - return policy.RaiseExceptionAsync(1, contextData, configureException, cancellationToken); - } + throw exception; + } + return TaskHelper.EmptyTask; + }, contextData, cancellationToken); + } + public static Task RaiseExceptionAsync(this AsyncPolicy policy, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() + { + return policy.RaiseExceptionAsync(1, contextData, configureException, cancellationToken); } -} \ No newline at end of file + +} diff --git a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs index 04e172850bb..73c8eaa2dec 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs @@ -2,57 +2,56 @@ using System.Collections.Generic; using System.Linq; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class ContextualPolicyTResultExtensions { - public static class ContextualPolicyTResultExtensions + public static TResult RaiseResultSequence(this Policy policy, + IDictionary contextData, + params TResult[] resultsToRaise) { - public static TResult RaiseResultSequence(this Policy policy, - IDictionary contextData, - params TResult[] resultsToRaise) - { - return policy.RaiseResultSequence(contextData, resultsToRaise.ToList()); - } + return policy.RaiseResultSequence(contextData, resultsToRaise.ToList()); + } - public static TResult RaiseResultSequence(this Policy policy, - IDictionary contextData, - IEnumerable resultsToRaise) - { - var enumerator = resultsToRaise.GetEnumerator(); + public static TResult RaiseResultSequence(this Policy policy, + IDictionary contextData, + IEnumerable resultsToRaise) + { + var enumerator = resultsToRaise.GetEnumerator(); - return policy.Execute(_ => - { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } - - return enumerator.Current; - }, contextData); - } - - public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, - IDictionary contextData, - params TResult[] resultsToRaise) + return policy.Execute(_ => { - return policy.RaiseResultSequenceOnExecuteAndCapture(contextData, resultsToRaise.ToList()); - } + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } - public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, - IDictionary contextData, - IEnumerable resultsToRaise) - { - var enumerator = resultsToRaise.GetEnumerator(); + return enumerator.Current; + }, contextData); + } - return policy.ExecuteAndCapture(_ => - { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } + public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, + IDictionary contextData, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceOnExecuteAndCapture(contextData, resultsToRaise.ToList()); + } + + public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, + IDictionary contextData, + IEnumerable resultsToRaise) + { + var enumerator = resultsToRaise.GetEnumerator(); - return enumerator.Current; - }, contextData); - } + return policy.ExecuteAndCapture(_ => + { + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } + return enumerator.Current; + }, contextData); } + } diff --git a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs index af6da4f44f6..666b607f44e 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs @@ -4,51 +4,50 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class ContextualPolicyTResultExtensionsAsync { - public static class ContextualPolicyTResultExtensionsAsync + + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, +IDictionary contextData, +params TResult[] resultsToRaise) { + return policy.RaiseResultSequenceAsync(contextData, CancellationToken.None, resultsToRaise.ToList()); + } - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, - IDictionary contextData, - params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceAsync(contextData, CancellationToken.None, resultsToRaise.ToList()); - } + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IDictionary contextData, CancellationToken cancellationToken, IEnumerable resultsToRaise) + { + var enumerator = resultsToRaise.GetEnumerator(); - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IDictionary contextData, CancellationToken cancellationToken, IEnumerable resultsToRaise) + return policy.ExecuteAsync((_, _) => { - var enumerator = resultsToRaise.GetEnumerator(); - - return policy.ExecuteAsync((_, _) => + if (!enumerator.MoveNext()) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } + + return Task.FromResult(enumerator.Current); + }, contextData, cancellationToken); + } - return Task.FromResult(enumerator.Current); - }, contextData, cancellationToken); - } + public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceOnExecuteAndCaptureAsync(contextData, resultsToRaise.ToList()); + } - public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceOnExecuteAndCaptureAsync(contextData, resultsToRaise.ToList()); - } + public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, IEnumerable resultsToRaise) + { + var enumerator = resultsToRaise.GetEnumerator(); - public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, IEnumerable resultsToRaise) + return policy.ExecuteAndCaptureAsync(_ => { - var enumerator = resultsToRaise.GetEnumerator(); - - return policy.ExecuteAndCaptureAsync(_ => + if (!enumerator.MoveNext()) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } - - return Task.FromResult(enumerator.Current); - }, contextData); - } + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } + + return Task.FromResult(enumerator.Current); + }, contextData); } } diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs index 464ca555a40..14888c80354 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs @@ -2,42 +2,41 @@ using System.Threading; using Polly.Utilities; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; + +internal static class AddBehaviourIfHandleEngine { - internal static class AddBehaviourIfHandleEngine + internal static TResult Implementation( + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Action> behaviourIfHandle, + Func action, + Context context, + CancellationToken cancellationToken) { - internal static TResult Implementation( - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Action> behaviourIfHandle, - Func action, - Context context, - CancellationToken cancellationToken) + try { - try - { - TResult result = action(context, cancellationToken); - - if (shouldHandleResultPredicates.AnyMatch(result)) - { - behaviourIfHandle(new DelegateResult(result)); - } + TResult result = action(context, cancellationToken); - return result; - } - catch (Exception ex) + if (shouldHandleResultPredicates.AnyMatch(result)) { - Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } - - behaviourIfHandle(new DelegateResult(handledException)); + behaviourIfHandle(new DelegateResult(result)); + } - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + return result; + } + catch (Exception ex) + { + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { throw; } + + behaviourIfHandle(new DelegateResult(handledException)); + + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; } } } diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs index 71b5709b49a..f996124ccc1 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs @@ -1,56 +1,55 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle -{ - internal class AddBehaviourIfHandlePolicy : Policy - { - private readonly Action _behaviourIfHandle; +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; - internal AddBehaviourIfHandlePolicy(Action behaviourIfHandle, PolicyBuilder policyBuilder) - : base(policyBuilder) - { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); - } +internal class AddBehaviourIfHandlePolicy : Policy +{ + private readonly Action _behaviourIfHandle; - protected override TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken) - { - return AddBehaviourIfHandleEngine.Implementation( - ExceptionPredicates, - ResultPredicates.None, - outcome => _behaviourIfHandle(outcome.Exception), - action, - context, - cancellationToken - ); - } + internal AddBehaviourIfHandlePolicy(Action behaviourIfHandle, PolicyBuilder policyBuilder) + : base(policyBuilder) + { + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); } - internal class AddBehaviourIfHandlePolicy : Policy + protected override TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken) { - private readonly Action> _behaviourIfHandle; + return AddBehaviourIfHandleEngine.Implementation( + ExceptionPredicates, + ResultPredicates.None, + outcome => _behaviourIfHandle(outcome.Exception), + action, + context, + cancellationToken + ); + } +} - internal AddBehaviourIfHandlePolicy( - Action> behaviourIfHandle, - PolicyBuilder policyBuilder) - : base(policyBuilder) - { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); - } +internal class AddBehaviourIfHandlePolicy : Policy +{ + private readonly Action> _behaviourIfHandle; - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - return AddBehaviourIfHandleEngine.Implementation( - ExceptionPredicates, - ResultPredicates, - _behaviourIfHandle, - action, - context, - cancellationToken - ); - } + internal AddBehaviourIfHandlePolicy( + Action> behaviourIfHandle, + PolicyBuilder policyBuilder) + : base(policyBuilder) + { + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + } + + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return AddBehaviourIfHandleEngine.Implementation( + ExceptionPredicates, + ResultPredicates, + _behaviourIfHandle, + action, + context, + cancellationToken + ); } } diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs index 38740e30877..926de95f811 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs @@ -1,21 +1,20 @@ using System; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; + +internal static class AddBehaviourIfHandleSyntax { - internal static class AddBehaviourIfHandleSyntax + internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action behaviourIfHandle) { - internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action behaviourIfHandle) - { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); - } + return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + } - internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action> behaviourIfHandle) - { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action> behaviourIfHandle) + { + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); - } + return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); } } diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs index 40801dec03f..ac3ce6365a7 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs @@ -3,43 +3,42 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; + +internal static class AsyncAddBehaviourIfHandleEngine { - internal static class AsyncAddBehaviourIfHandleEngine + internal static async Task ImplementationAsync( + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Func, Task> behaviourIfHandle, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) { - internal static async Task ImplementationAsync( - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Func, Task> behaviourIfHandle, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) + try { - try - { - TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - - if (shouldHandleResultPredicates.AnyMatch(result)) - { - await behaviourIfHandle(new DelegateResult(result)).ConfigureAwait(continueOnCapturedContext); - } + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - return result; - } - catch (Exception ex) + if (shouldHandleResultPredicates.AnyMatch(result)) { - Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } - - await behaviourIfHandle(new DelegateResult(ex)).ConfigureAwait(continueOnCapturedContext); + await behaviourIfHandle(new DelegateResult(result)).ConfigureAwait(continueOnCapturedContext); + } - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + return result; + } + catch (Exception ex) + { + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { throw; } + + await behaviourIfHandle(new DelegateResult(ex)).ConfigureAwait(continueOnCapturedContext); + + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; } } } diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs index c5489d34cf1..9493cb0c8de 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs @@ -1,60 +1,59 @@ using System; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; + +internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy +{ + private readonly Func _behaviourIfHandle; + + internal AsyncAddBehaviourIfHandlePolicy( + Func behaviourIfHandle, + PolicyBuilder policyBuilder) + : base(policyBuilder) + { + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + } + + protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( + ExceptionPredicates, + ResultPredicates.None, + outcome => _behaviourIfHandle(outcome.Exception), + action, + context, + cancellationToken, + continueOnCapturedContext + ); + } +} + +internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy { - internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy + private readonly Func, Task> _behaviourIfHandle; + + internal AsyncAddBehaviourIfHandlePolicy( + Func, Task> behaviourIfHandle, + PolicyBuilder policyBuilder) + : base(policyBuilder) { - private readonly Func _behaviourIfHandle; - - internal AsyncAddBehaviourIfHandlePolicy( - Func behaviourIfHandle, - PolicyBuilder policyBuilder) - : base(policyBuilder) - { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); - } - - protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( - ExceptionPredicates, - ResultPredicates.None, - outcome => _behaviourIfHandle(outcome.Exception), - action, - context, - cancellationToken, - continueOnCapturedContext - ); - } + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + } - internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy + protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, + bool continueOnCapturedContext) { - private readonly Func, Task> _behaviourIfHandle; - - internal AsyncAddBehaviourIfHandlePolicy( - Func, Task> behaviourIfHandle, - PolicyBuilder policyBuilder) - : base(policyBuilder) - { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); - - } - - protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( - ExceptionPredicates, - ResultPredicates, - _behaviourIfHandle, - action, - context, - cancellationToken, - continueOnCapturedContext - ); - } + return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( + ExceptionPredicates, + ResultPredicates, + _behaviourIfHandle, + action, + context, + cancellationToken, + continueOnCapturedContext + ); } } diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs index d16499b4b5a..b450a85ce8a 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs @@ -1,26 +1,25 @@ using System; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; + +internal static class AsyncAddBehaviourIfHandleSyntax { - internal static class AsyncAddBehaviourIfHandleSyntax + internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( + this PolicyBuilder policyBuilder, + Func behaviourIfHandle) { - internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( - this PolicyBuilder policyBuilder, - Func behaviourIfHandle) - { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); - } + return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + } - internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( - this PolicyBuilder policyBuilder, - Func, Task> behaviourIfHandle) - { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( + this PolicyBuilder policyBuilder, + Func, Task> behaviourIfHandle) + { + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); - } + return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); } } diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs index 4f41915a540..9a585f9f9c9 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs @@ -2,32 +2,31 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.PreExecute +namespace Polly.Specs.Helpers.Custom.PreExecute; + +internal static class AsyncPreExecuteEngine { - internal static class AsyncPreExecuteEngine + internal static async Task ImplementationAsync( + Func preExecute, + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) { - internal static async Task ImplementationAsync( - Func preExecute, - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); + await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); - await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } - internal static async Task ImplementationAsync( - Func preExecute, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); + internal static async Task ImplementationAsync( + Func preExecute, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs index 15e49b2c35d..81b599c1a0a 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs @@ -2,47 +2,46 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.PreExecute +namespace Polly.Specs.Helpers.Custom.PreExecute; + +internal class AsyncPreExecutePolicy : AsyncPolicy +{ + private Func _preExecute; + + public static AsyncPreExecutePolicy CreateAsync(Func preExecute) + { + return new AsyncPreExecutePolicy(preExecute); + } + + internal AsyncPreExecutePolicy(Func preExecute) + { + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } + + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + } +} + +internal class AsyncPreExecutePolicy : AsyncPolicy { - internal class AsyncPreExecutePolicy : AsyncPolicy + private Func _preExecute; + + public static AsyncPreExecutePolicy CreateAsync(Func preExecute) + { + return new AsyncPreExecutePolicy(preExecute); + } + + internal AsyncPreExecutePolicy(Func preExecute) { - private Func _preExecute; - - public static AsyncPreExecutePolicy CreateAsync(Func preExecute) - { - return new AsyncPreExecutePolicy(preExecute); - } - - internal AsyncPreExecutePolicy(Func preExecute) - { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); - } - - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); - } + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); } - internal class AsyncPreExecutePolicy : AsyncPolicy + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) { - private Func _preExecute; - - public static AsyncPreExecutePolicy CreateAsync(Func preExecute) - { - return new AsyncPreExecutePolicy(preExecute); - } - - internal AsyncPreExecutePolicy(Func preExecute) - { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); - } - - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); - } + return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs index 9a4a787d877..30313751e62 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs @@ -1,30 +1,29 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers.Custom.PreExecute +namespace Polly.Specs.Helpers.Custom.PreExecute; + +internal static class PreExecuteEngine { - internal static class PreExecuteEngine + internal static void Implementation( + Action preExecute, + Action action, + Context context, + CancellationToken cancellationToken) { - internal static void Implementation( - Action preExecute, - Action action, - Context context, - CancellationToken cancellationToken) - { - preExecute?.Invoke(); + preExecute?.Invoke(); - action(context, cancellationToken); - } + action(context, cancellationToken); + } - internal static TResult Implementation( - Action preExecute, - Func action, - Context context, - CancellationToken cancellationToken) - { - preExecute?.Invoke(); + internal static TResult Implementation( + Action preExecute, + Func action, + Context context, + CancellationToken cancellationToken) + { + preExecute?.Invoke(); - return action(context, cancellationToken); - } + return action(context, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs index d41d9c3719c..e27504eb96e 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs @@ -1,45 +1,44 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers.Custom.PreExecute +namespace Polly.Specs.Helpers.Custom.PreExecute; + +internal class PreExecutePolicy : Policy +{ + private Action _preExecute; + + public static PreExecutePolicy Create(Action preExecute) + { + return new PreExecutePolicy(preExecute); + } + + internal PreExecutePolicy(Action preExecute) + { + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } + + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + } +} + +internal class PreExecutePolicy : Policy { - internal class PreExecutePolicy : Policy + private Action _preExecute; + + public static PreExecutePolicy Create(Action preExecute) + { + return new PreExecutePolicy(preExecute); + } + + internal PreExecutePolicy(Action preExecute) { - private Action _preExecute; - - public static PreExecutePolicy Create(Action preExecute) - { - return new PreExecutePolicy(preExecute); - } - - internal PreExecutePolicy(Action preExecute) - { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); - } - - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); - } + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); } - internal class PreExecutePolicy : Policy + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) { - private Action _preExecute; - - public static PreExecutePolicy Create(Action preExecute) - { - return new PreExecutePolicy(preExecute); - } - - internal PreExecutePolicy(Action preExecute) - { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); - } - - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); - } + return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/ObjectExtensions.cs b/src/Polly.Specs/Helpers/ObjectExtensions.cs index 3f330295935..c01ad7b6eab 100644 --- a/src/Polly.Specs/Helpers/ObjectExtensions.cs +++ b/src/Polly.Specs/Helpers/ObjectExtensions.cs @@ -2,17 +2,16 @@ using System.Reflection; using System.Linq; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class ObjectExtensions { - public static class ObjectExtensions + public static IDictionary AsDictionary(this object source) { - public static IDictionary AsDictionary(this object source) - { - return source.GetType().GetRuntimeProperties().ToDictionary - ( - propInfo => propInfo.Name, - propInfo => propInfo.GetValue(source, null) - ); - } + return source.GetType().GetRuntimeProperties().ToDictionary + ( + propInfo => propInfo.Name, + propInfo => propInfo.GetValue(source, null) + ); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/PolicyExtensions.cs b/src/Polly.Specs/Helpers/PolicyExtensions.cs index 95798ba6746..05a44ea2bef 100644 --- a/src/Polly.Specs/Helpers/PolicyExtensions.cs +++ b/src/Polly.Specs/Helpers/PolicyExtensions.cs @@ -1,123 +1,122 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class PolicyExtensions { - public static class PolicyExtensions + public class ExceptionAndOrCancellationScenario { - public class ExceptionAndOrCancellationScenario - { - public int NumberOfTimesToRaiseException; - public int? AttemptDuringWhichToCancel; - public bool ActionObservesCancellation = true; - } + public int NumberOfTimesToRaiseException; + public int? AttemptDuringWhichToCancel; + public bool ActionObservesCancellation = true; + } - public static void RaiseException(this Policy policy, TException instance) where TException : Exception + public static void RaiseException(this Policy policy, TException instance) where TException : Exception + { + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { - ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario - { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = 1 - }; + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = 1 + }; - policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, _ => instance); - } + policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, _ => instance); + } - public static void RaiseException(this Policy policy, Action configureException = null) where TException : Exception, new() + public static void RaiseException(this Policy policy, Action configureException = null) where TException : Exception, new() + { + policy.RaiseException(1, configureException); + } + + public static void RaiseException(this Policy policy, int numberOfTimesToRaiseException, Action configureException = null) where TException : Exception, new() + { + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { - policy.RaiseException(1, configureException); - } + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = numberOfTimesToRaiseException + }; - public static void RaiseException(this Policy policy, int numberOfTimesToRaiseException, Action configureException = null) where TException : Exception, new() + Func exceptionFactory = i => { - ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario - { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = numberOfTimesToRaiseException - }; + var exception = new TException(); + configureException?.Invoke(exception, i); + return exception; + }; - Func exceptionFactory = i => - { - var exception = new TException(); - configureException?.Invoke(exception, i); - return exception; - }; + policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); + } - policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); - } + public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() + { + policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, _ => new TException()); + } - public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() - { - policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, _ => new TException()); - } + public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() + { + return policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, + _ => new TException(), successResult); + } - public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() - { - return policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, - _ => new TException(), successResult); - } + public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception + { + int counter = 0; + + CancellationToken cancellationToken = cancellationTokenSource.Token; - public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception + policy.Execute(ct => { - int counter = 0; + onExecute(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + counter++; - policy.Execute(ct => + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) { - onExecute(); + cancellationTokenSource.Cancel(); + } - counter++; + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + }, cancellationToken); + } - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception + { + int counter = 0; - }, cancellationToken); - } + CancellationToken cancellationToken = cancellationTokenSource.Token; - public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception + return policy.Execute(ct => { - int counter = 0; + onExecute(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + counter++; - return policy.Execute(ct => + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) { - onExecute(); - - counter++; + cancellationTokenSource.Cancel(); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } - - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - return successResult; - }, cancellationToken); - } + return successResult; + }, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs index d60d7fa9974..6b65faca507 100644 --- a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs @@ -3,126 +3,125 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class PolicyExtensionsAsync { - public static class PolicyExtensionsAsync + public class ExceptionAndOrCancellationScenario { - public class ExceptionAndOrCancellationScenario - { - public int NumberOfTimesToRaiseException; + public int NumberOfTimesToRaiseException; - public int? AttemptDuringWhichToCancel; + public int? AttemptDuringWhichToCancel; - public bool ActionObservesCancellation = true; - } + public bool ActionObservesCancellation = true; + } - public static Task RaiseExceptionAsync(this AsyncPolicy policy, TException instance) where TException : Exception + public static Task RaiseExceptionAsync(this AsyncPolicy policy, TException instance) where TException : Exception + { + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { - ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario - { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = 1 - }; + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = 1 + }; - return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, _ => instance); - } + return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, _ => instance); + } - public static Task RaiseExceptionAsync(this AsyncPolicy policy, Action configureException = null) where TException : Exception, new() + public static Task RaiseExceptionAsync(this AsyncPolicy policy, Action configureException = null) where TException : Exception, new() + { + return policy.RaiseExceptionAsync(1, configureException); + } + + public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() + { + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { - return policy.RaiseExceptionAsync(1, configureException); - } + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = numberOfTimesToRaiseException + }; - public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() + Func exceptionFactory = i => { - ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario - { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = numberOfTimesToRaiseException - }; + var exception = new TException(); + configureException?.Invoke(exception, i); + return exception; + }; - Func exceptionFactory = i => - { - var exception = new TException(); - configureException?.Invoke(exception, i); - return exception; - }; + return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); + } - return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); - } + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() + { + return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, _ => new TException()); + } - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() - { - return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, _ => new TException()); - } + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() + { + return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + _ => new TException(), successResult); + } - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() - { - return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - _ => new TException(), successResult); - } + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception + { + int counter = 0; + + CancellationToken cancellationToken = cancellationTokenSource.Token; - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception + return policy.ExecuteAsync(ct => { - int counter = 0; + onExecute(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + counter++; - return policy.ExecuteAsync(ct => + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) { - onExecute(); + cancellationTokenSource.Cancel(); + } - counter++; + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + return TaskHelper.EmptyTask; + }, cancellationToken); + } - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception + { + int counter = 0; - return TaskHelper.EmptyTask; - }, cancellationToken); - } + CancellationToken cancellationToken = cancellationTokenSource.Token; - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception + return policy.ExecuteAsync(ct => { - int counter = 0; + onExecute(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + counter++; - return policy.ExecuteAsync(ct => + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) { - onExecute(); - - counter++; + cancellationTokenSource.Cancel(); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } - - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - return Task.FromResult(successResult); - }, cancellationToken); - } + return Task.FromResult(successResult); + }, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs index 0491df6848e..18b90d316f8 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs @@ -4,113 +4,112 @@ using System.Threading; using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class PolicyTResultExtensions { - public static class PolicyTResultExtensions + public static TResult RaiseResultSequence(this Policy policy, params TResult[] resultsToRaise) { - public static TResult RaiseResultSequence(this Policy policy, params TResult[] resultsToRaise) - { - return policy.RaiseResultSequence(resultsToRaise.ToList()); - } + return policy.RaiseResultSequence(resultsToRaise.ToList()); + } - public static TResult RaiseResultSequence(this Policy policy, IEnumerable resultsToRaise) + public static TResult RaiseResultSequence(this Policy policy, IEnumerable resultsToRaise) + { + using (var enumerator = resultsToRaise.GetEnumerator()) { - using (var enumerator = resultsToRaise.GetEnumerator()) + return policy.Execute(() => { - return policy.Execute(() => + if (!enumerator.MoveNext()) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } - - return enumerator.Current; - }); - } - } + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } - public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, params object[] resultsOrExceptionsToRaise) - { - return policy.RaiseResultAndOrExceptionSequence(resultsOrExceptionsToRaise.ToList()); + return enumerator.Current; + }); } + } - public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, - IEnumerable resultsOrExceptionsToRaise) + public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, params object[] resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequence(resultsOrExceptionsToRaise.ToList()); + } + + public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, + IEnumerable resultsOrExceptionsToRaise) + { + using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) { - using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) + + return policy.Execute(() => { + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); + } - return policy.Execute(() => + object current = enumerator.Current; + if (current is Exception) + { + throw (Exception) current; + } + else if (current is TResult) + { + return (TResult) current; + } + else { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); - } - - object current = enumerator.Current; - if (current is Exception) - { - throw (Exception) current; - } - else if (current is TResult) - { - return (TResult) current; - } - else - { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); - } - }); - } + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); + } + }); } + } - public class ResultAndOrCancellationScenario - { - public int? AttemptDuringWhichToCancel = null; + public class ResultAndOrCancellationScenario + { + public int? AttemptDuringWhichToCancel = null; - public bool ActionObservesCancellation = true; - } + public bool ActionObservesCancellation = true; + } - public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, - Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, - params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - resultsToRaise.ToList()); - } + public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, + Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + resultsToRaise.ToList()); + } - public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, IEnumerable resultsToRaise) - { - int counter = 0; + public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, IEnumerable resultsToRaise) + { + int counter = 0; - CancellationToken cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; - using (var enumerator = resultsToRaise.GetEnumerator()) + using (var enumerator = resultsToRaise.GetEnumerator()) + { + return policy.Execute(ct => { - return policy.Execute(ct => - { - onExecute(); + onExecute(); - counter++; + counter++; - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - return enumerator.Current; - }, cancellationToken); - } + return enumerator.Current; + }, cancellationToken); } } } diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs index e9130ca9cf4..614c502db99 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs @@ -6,127 +6,126 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +public static class PolicyTResultExtensionsAsync { - public static class PolicyTResultExtensionsAsync + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, params TResult[] resultsToRaise) { - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceAsync(resultsToRaise.ToList()); - } + return policy.RaiseResultSequenceAsync(resultsToRaise.ToList()); + } - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IEnumerable resultsToRaise) - { - return policy.RaiseResultSequenceAsync(default, resultsToRaise); - } + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IEnumerable resultsToRaise) + { + return policy.RaiseResultSequenceAsync(default, resultsToRaise); + } - public static async Task RaiseResultSequenceAsync(this AsyncPolicy policy, - CancellationToken cancellationToken, IEnumerable resultsToRaise) + public static async Task RaiseResultSequenceAsync(this AsyncPolicy policy, + CancellationToken cancellationToken, IEnumerable resultsToRaise) + { + using (var enumerator = resultsToRaise.GetEnumerator()) { - using (var enumerator = resultsToRaise.GetEnumerator()) + return await policy.ExecuteAsync(_ => { - return await policy.ExecuteAsync(_ => + if (!enumerator.MoveNext()) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } - - return Task.FromResult(enumerator.Current); - }, cancellationToken); - } - } + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } - public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, params object[] resultsOrExceptionsToRaise) - { - return policy.RaiseResultAndOrExceptionSequenceAsync(resultsOrExceptionsToRaise.ToList()); + return Task.FromResult(enumerator.Current); + }, cancellationToken); } + } - public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, - IEnumerable resultsOrExceptionsToRaise) - { - return policy.RaiseResultAndOrExceptionSequenceAsync(CancellationToken.None, resultsOrExceptionsToRaise); - } + public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, params object[] resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequenceAsync(resultsOrExceptionsToRaise.ToList()); + } - public static async Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, - CancellationToken cancellationToken, IEnumerable resultsOrExceptionsToRaise) + public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, + IEnumerable resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequenceAsync(CancellationToken.None, resultsOrExceptionsToRaise); + } + + public static async Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, + CancellationToken cancellationToken, IEnumerable resultsOrExceptionsToRaise) + { + using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) { - using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) + return await policy.ExecuteAsync(_ => { - return await policy.ExecuteAsync(_ => + if (!enumerator.MoveNext()) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); - } - - object current = enumerator.Current; - if (current is Exception) - { - throw (Exception) current; - } - else if (current is TResult) - { - return Task.FromResult((TResult) current); - } - else - { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), - $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); - } - }, cancellationToken); - } + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); + } + + object current = enumerator.Current; + if (current is Exception) + { + throw (Exception) current; + } + else if (current is TResult) + { + return Task.FromResult((TResult) current); + } + else + { + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), + $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); + } + }, cancellationToken); } + } - public class ResultAndOrCancellationScenario - { - public int? AttemptDuringWhichToCancel = null; + public class ResultAndOrCancellationScenario + { + public int? AttemptDuringWhichToCancel = null; - public bool ActionObservesCancellation = true; - } + public bool ActionObservesCancellation = true; + } - public static Task RaiseResultSequenceAndOrCancellationAsync(this AsyncPolicy policy, - Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, - params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - resultsToRaise.ToList()); - } + public static Task RaiseResultSequenceAndOrCancellationAsync(this AsyncPolicy policy, + Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + resultsToRaise.ToList()); + } - public static async Task RaiseResultSequenceAndOrCancellationAsync( - this AsyncPolicy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, - Action onExecute, IEnumerable resultsToRaise) - { - int counter = 0; + public static async Task RaiseResultSequenceAndOrCancellationAsync( + this AsyncPolicy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, + Action onExecute, IEnumerable resultsToRaise) + { + int counter = 0; - CancellationToken cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; - using (var enumerator = resultsToRaise.GetEnumerator()) + using (var enumerator = resultsToRaise.GetEnumerator()) + { + return await policy.ExecuteAsync(ct => { - return await policy.ExecuteAsync(ct => - { - onExecute(); + onExecute(); - counter++; + counter++; - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - return Task.FromResult(enumerator.Current); - }, cancellationToken); - } + return Task.FromResult(enumerator.Current); + }, cancellationToken); } } } diff --git a/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs b/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs index 64da0965670..ec2fbf32d05 100644 --- a/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs +++ b/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs @@ -2,39 +2,38 @@ using FluentAssertions; using Polly.RateLimit; -namespace Polly.Specs.Helpers.RateLimit +namespace Polly.Specs.Helpers.RateLimit; + +internal static class IRateLimiterExtensions { - internal static class IRateLimiterExtensions + public static void ShouldPermitAnExecution(this IRateLimiter rateLimiter) { - public static void ShouldPermitAnExecution(this IRateLimiter rateLimiter) - { - (bool permitExecution, TimeSpan retryAfter) canExecute = rateLimiter.PermitExecution(); + (bool permitExecution, TimeSpan retryAfter) canExecute = rateLimiter.PermitExecution(); - canExecute.permitExecution.Should().BeTrue(); - canExecute.retryAfter.Should().Be(TimeSpan.Zero); - } + canExecute.permitExecution.Should().BeTrue(); + canExecute.retryAfter.Should().Be(TimeSpan.Zero); + } - public static void ShouldPermitNExecutions(this IRateLimiter rateLimiter, long numberOfExecutions) + public static void ShouldPermitNExecutions(this IRateLimiter rateLimiter, long numberOfExecutions) + { + for (int execution = 0; execution < numberOfExecutions; execution++) { - for (int execution = 0; execution < numberOfExecutions; execution++) - { - rateLimiter.ShouldPermitAnExecution(); - } + rateLimiter.ShouldPermitAnExecution(); } + } - public static void ShouldNotPermitAnExecution(this IRateLimiter rateLimiter, TimeSpan? retryAfter = null) - { - (bool permitExecution, TimeSpan retryAfter) canExecute = rateLimiter.PermitExecution(); + public static void ShouldNotPermitAnExecution(this IRateLimiter rateLimiter, TimeSpan? retryAfter = null) + { + (bool permitExecution, TimeSpan retryAfter) canExecute = rateLimiter.PermitExecution(); - canExecute.permitExecution.Should().BeFalse(); - if (retryAfter == null) - { - canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); - } - else - { - canExecute.retryAfter.Should().Be(retryAfter.Value); - } + canExecute.permitExecution.Should().BeFalse(); + if (retryAfter == null) + { + canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); + } + else + { + canExecute.retryAfter.Should().Be(retryAfter.Value); } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs b/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs index fcb1c72c46d..96797f86d15 100644 --- a/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs +++ b/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs @@ -1,21 +1,20 @@ using System; -namespace Polly.Specs.Helpers.RateLimit +namespace Polly.Specs.Helpers.RateLimit; + +internal class ResultClassWithRetryAfter : ResultClass { - internal class ResultClassWithRetryAfter : ResultClass - { - public TimeSpan RetryAfter { get; } + public TimeSpan RetryAfter { get; } - public ResultClassWithRetryAfter(ResultPrimitive result) - : base(result) - { - RetryAfter = TimeSpan.Zero; - } + public ResultClassWithRetryAfter(ResultPrimitive result) + : base(result) + { + RetryAfter = TimeSpan.Zero; + } - public ResultClassWithRetryAfter(TimeSpan retryAfter) - : base(ResultPrimitive.Undefined) - { - RetryAfter = retryAfter; - } + public ResultClassWithRetryAfter(TimeSpan retryAfter) + : base(ResultPrimitive.Undefined) + { + RetryAfter = retryAfter; } } diff --git a/src/Polly.Specs/Helpers/ResultClass.cs b/src/Polly.Specs/Helpers/ResultClass.cs index e07ed99eed5..31bbbefe844 100644 --- a/src/Polly.Specs/Helpers/ResultClass.cs +++ b/src/Polly.Specs/Helpers/ResultClass.cs @@ -1,22 +1,21 @@ -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +/// +/// A helper class supporting tests on how Policy<TResult> policies handle return results which are class types (as opposed to primitive types). +/// +internal class ResultClass { - /// - /// A helper class supporting tests on how Policy<TResult> policies handle return results which are class types (as opposed to primitive types). - /// - internal class ResultClass + public ResultClass(ResultPrimitive resultCode) { - public ResultClass(ResultPrimitive resultCode) - { - ResultCode = resultCode; - } + ResultCode = resultCode; + } - public ResultClass(ResultPrimitive resultCode, string someString) : this(resultCode) - { - SomeString = someString; - } + public ResultClass(ResultPrimitive resultCode, string someString) : this(resultCode) + { + SomeString = someString; + } - public ResultPrimitive ResultCode { get; set; } + public ResultPrimitive ResultCode { get; set; } - public string SomeString { get; set; } - } + public string SomeString { get; set; } } diff --git a/src/Polly.Specs/Helpers/ResultPrimitive.cs b/src/Polly.Specs/Helpers/ResultPrimitive.cs index d605b2aad8a..a8438f54487 100644 --- a/src/Polly.Specs/Helpers/ResultPrimitive.cs +++ b/src/Polly.Specs/Helpers/ResultPrimitive.cs @@ -1,17 +1,16 @@ -namespace Polly.Specs.Helpers +namespace Polly.Specs.Helpers; + +/// +/// A helper class supporting tests on how Policy<TResult> policies may handle return results which are primitive types such as ints or enums. +/// +internal enum ResultPrimitive { - /// - /// A helper class supporting tests on how Policy<TResult> policies may handle return results which are primitive types such as ints or enums. - /// - internal enum ResultPrimitive - { - Undefined, - Fault, - Good, - FaultAgain, - GoodAgain, - FaultYetAgain, - Substitute, - WhateverButTooLate - } + Undefined, + Fault, + Good, + FaultAgain, + GoodAgain, + FaultYetAgain, + Substitute, + WhateverButTooLate } diff --git a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs index 70116521bd1..35288378492 100644 --- a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs +++ b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs @@ -5,33 +5,32 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class IAsyncPolicyExtensionsSpecs { - public class IAsyncPolicyExtensionsSpecs + [Fact] + public void Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_a_generic_IAsyncPolicyTResult() { - [Fact] - public void Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_a_generic_IAsyncPolicyTResult() - { - IAsyncPolicy nonGenericPolicy = Policy.TimeoutAsync(10); - var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); + IAsyncPolicy nonGenericPolicy = Policy.TimeoutAsync(10); + var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); - genericPolicy.Should().BeAssignableTo>(); - } + genericPolicy.Should().BeAssignableTo>(); + } - [Fact] - public async Task Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_an_IAsyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() - { - // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. + [Fact] + public async Task Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_an_IAsyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() + { + // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero); - IAsyncPolicy nonGenericPolicy = breaker; - var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); - Func> deleg = () => Task.FromResult(ResultPrimitive.Good); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero); + IAsyncPolicy nonGenericPolicy = breaker; + var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); + Func> deleg = () => Task.FromResult(ResultPrimitive.Good); - (await genericPolicy.ExecuteAsync(deleg)).Should().Be(ResultPrimitive.Good); - breaker.Isolate(); - await genericPolicy.Awaiting(p => p.ExecuteAsync(deleg)).Should().ThrowAsync(); - } + (await genericPolicy.ExecuteAsync(deleg)).Should().Be(ResultPrimitive.Good); + breaker.Isolate(); + await genericPolicy.Awaiting(p => p.ExecuteAsync(deleg)).Should().ThrowAsync(); } - } + diff --git a/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs b/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs index 88d2bad937f..5103728b4d4 100644 --- a/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs +++ b/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs @@ -4,33 +4,32 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class ISyncPolicyExtensionsSpecs { - public class ISyncPolicyExtensionsSpecs + [Fact] + public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_a_generic_ISyncPolicyTResult() { - [Fact] - public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_a_generic_ISyncPolicyTResult() - { - ISyncPolicy nonGenericPolicy = Policy.Timeout(10); - var genericPolicy = nonGenericPolicy.AsPolicy(); + ISyncPolicy nonGenericPolicy = Policy.Timeout(10); + var genericPolicy = nonGenericPolicy.AsPolicy(); - genericPolicy.Should().BeAssignableTo>(); - } + genericPolicy.Should().BeAssignableTo>(); + } - [Fact] - public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_an_ISyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() - { - // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. + [Fact] + public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_an_ISyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() + { + // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. - CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - ISyncPolicy nonGenericPolicy = breaker; - var genericPolicy = nonGenericPolicy.AsPolicy(); - Func deleg = () => ResultPrimitive.Good; + CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + ISyncPolicy nonGenericPolicy = breaker; + var genericPolicy = nonGenericPolicy.AsPolicy(); + Func deleg = () => ResultPrimitive.Good; - genericPolicy.Execute(deleg).Should().Be(ResultPrimitive.Good); - breaker.Isolate(); - genericPolicy.Invoking(p => p.Execute(deleg)).Should().Throw(); - } + genericPolicy.Execute(deleg).Should().Be(ResultPrimitive.Good); + breaker.Isolate(); + genericPolicy.Invoking(p => p.Execute(deleg)).Should().Throw(); } - } + diff --git a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs index c11807ebaba..71b1dfef5c0 100644 --- a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs @@ -4,39 +4,38 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.NoOp +namespace Polly.Specs.NoOp; + +public class NoOpAsyncSpecs { - public class NoOpAsyncSpecs + [Fact] + public async Task Should_execute_user_delegate() { - [Fact] - public async Task Should_execute_user_delegate() - { - var policy = Policy.NoOpAsync(); - bool executed = false; - - await policy.Awaiting(p => p.ExecuteAsync(() => { executed = true; return TaskHelper.EmptyTask; })) - .Should().NotThrowAsync(); + var policy = Policy.NoOpAsync(); + bool executed = false; - executed.Should().BeTrue(); - } + await policy.Awaiting(p => p.ExecuteAsync(() => { executed = true; return TaskHelper.EmptyTask; })) + .Should().NotThrowAsync(); - [Fact] - public async Task Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - var policy = Policy.NoOpAsync(); + executed.Should().BeTrue(); + } - bool executed = false; + [Fact] + public async Task Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + { + var policy = Policy.NoOpAsync(); - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + bool executed = false; - await policy.Awaiting(p => p.ExecuteAsync( - _ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) - .Should().NotThrowAsync(); - } + using (CancellationTokenSource cts = new CancellationTokenSource()) + { + cts.Cancel(); - executed.Should().BeTrue(); + await policy.Awaiting(p => p.ExecuteAsync( + _ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) + .Should().NotThrowAsync(); } + + executed.Should().BeTrue(); } } diff --git a/src/Polly.Specs/NoOp/NoOpSpecs.cs b/src/Polly.Specs/NoOp/NoOpSpecs.cs index 315f7f63851..7c16ee57b93 100644 --- a/src/Polly.Specs/NoOp/NoOpSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpSpecs.cs @@ -3,37 +3,36 @@ using Polly.NoOp; using Xunit; -namespace Polly.Specs.NoOp +namespace Polly.Specs.NoOp; + +public class NoOpSpecs { - public class NoOpSpecs + [Fact] + public void Should_execute_user_delegate() { - [Fact] - public void Should_execute_user_delegate() - { - NoOpPolicy policy = Policy.NoOp(); - bool executed = false; - - policy.Invoking(x => x.Execute(() => { executed = true; })) - .Should().NotThrow(); + NoOpPolicy policy = Policy.NoOp(); + bool executed = false; - executed.Should().BeTrue(); - } + policy.Invoking(x => x.Execute(() => { executed = true; })) + .Should().NotThrow(); - [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - NoOpPolicy policy = Policy.NoOp(); - bool executed = false; + executed.Should().BeTrue(); + } - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + [Fact] + public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + { + NoOpPolicy policy = Policy.NoOp(); + bool executed = false; - policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) - .Should().NotThrow(); - } + using (CancellationTokenSource cts = new CancellationTokenSource()) + { + cts.Cancel(); - executed.Should().BeTrue(); + policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) + .Should().NotThrow(); } + + executed.Should().BeTrue(); } } diff --git a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs index 10f81a209dd..0cfb27d1896 100644 --- a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs @@ -5,41 +5,40 @@ using Polly.NoOp; using Xunit; -namespace Polly.Specs.NoOp +namespace Polly.Specs.NoOp; + +public class NoOpTResultAsyncSpecs { - public class NoOpTResultAsyncSpecs + [Fact] + public async Task Should_execute_user_delegate() { - [Fact] - public async Task Should_execute_user_delegate() - { - var policy = Policy.NoOpAsync(); - int? result = null; + var policy = Policy.NoOpAsync(); + int? result = null; - Func, Task> action = async p => result = await p.ExecuteAsync(() => Task.FromResult((int?)10)); - await policy.Awaiting(action) - .Should().NotThrowAsync(); + Func, Task> action = async p => result = await p.ExecuteAsync(() => Task.FromResult((int?)10)); + await policy.Awaiting(action) + .Should().NotThrowAsync(); - result.HasValue.Should().BeTrue(); - result.Should().Be(10); - } - - [Fact] - public async Task Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - var policy = Policy.NoOpAsync(); - int? result = null; + result.HasValue.Should().BeTrue(); + result.Should().Be(10); + } - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + [Fact] + public async Task Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + { + var policy = Policy.NoOpAsync(); + int? result = null; - Func, Task> action = async p => result = await p.ExecuteAsync(_ => Task.FromResult((int?)10), cts.Token); - await policy.Awaiting(action) - .Should().NotThrowAsync(); - } + using (CancellationTokenSource cts = new CancellationTokenSource()) + { + cts.Cancel(); - result.HasValue.Should().BeTrue(); - result.Should().Be(10); + Func, Task> action = async p => result = await p.ExecuteAsync(_ => Task.FromResult((int?)10), cts.Token); + await policy.Awaiting(action) + .Should().NotThrowAsync(); } + + result.HasValue.Should().BeTrue(); + result.Should().Be(10); } } diff --git a/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs b/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs index d5da3e596b7..9c940f9552b 100644 --- a/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs @@ -3,39 +3,38 @@ using Polly.NoOp; using Xunit; -namespace Polly.Specs.NoOp +namespace Polly.Specs.NoOp; + +public class NoOpTResultSpecs { - public class NoOpTResultSpecs + [Fact] + public void Should_execute_user_delegate() { - [Fact] - public void Should_execute_user_delegate() - { - NoOpPolicy policy = Policy.NoOp(); - int? result = null; + NoOpPolicy policy = Policy.NoOp(); + int? result = null; - policy.Invoking(x => result = x.Execute(() => 10)) - .Should().NotThrow(); + policy.Invoking(x => result = x.Execute(() => 10)) + .Should().NotThrow(); - result.HasValue.Should().BeTrue(); - result.Should().Be(10); - } - - [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - NoOpPolicy policy = Policy.NoOp(); - int? result = null; + result.HasValue.Should().BeTrue(); + result.Should().Be(10); + } - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + [Fact] + public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + { + NoOpPolicy policy = Policy.NoOp(); + int? result = null; - policy.Invoking(p => result = p.Execute(_ => 10, cts.Token)) - .Should().NotThrow(); - } + using (CancellationTokenSource cts = new CancellationTokenSource()) + { + cts.Cancel(); - result.HasValue.Should().BeTrue(); - result.Should().Be(10); + policy.Invoking(p => result = p.Execute(_ => 10, cts.Token)) + .Should().NotThrow(); } + + result.HasValue.Should().BeTrue(); + result.Should().Be(10); } } diff --git a/src/Polly.Specs/PolicyAsyncSpecs.cs b/src/Polly.Specs/PolicyAsyncSpecs.cs index ac645347500..987c027fa25 100644 --- a/src/Polly.Specs/PolicyAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyAsyncSpecs.cs @@ -5,297 +5,296 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class PolicyAsyncSpecs { - public class PolicyAsyncSpecs + #region Execute tests + + [Fact] + public async Task Executing_the_policy_action_should_execute_the_specified_async_action() { - #region Execute tests + bool executed = false; - [Fact] - public async Task Executing_the_policy_action_should_execute_the_specified_async_action() - { - bool executed = false; + var policy = Policy + .Handle() + .RetryAsync((_, _) => { }); - var policy = Policy - .Handle() - .RetryAsync((_, _) => { }); + await policy.ExecuteAsync(() => + { + executed = true; + return TaskHelper.EmptyTask; + }); - await policy.ExecuteAsync(() => - { - executed = true; - return TaskHelper.EmptyTask; - }); + executed.Should() + .BeTrue(); + } - executed.Should() - .BeTrue(); - } + [Fact] + public async Task Executing_the_policy_function_should_execute_the_specified_async_function_and_return_the_result() + { + var policy = Policy + .Handle() + .RetryAsync((_, _) => { }); - [Fact] - public async Task Executing_the_policy_function_should_execute_the_specified_async_function_and_return_the_result() - { - var policy = Policy - .Handle() - .RetryAsync((_, _) => { }); + int result = await policy.ExecuteAsync(() => Task.FromResult(2)); - int result = await policy.ExecuteAsync(() => Task.FromResult(2)); + result.Should() + .Be(2); + } - result.Should() - .Be(2); - } + #endregion - #endregion + #region ExecuteAndCapture tests - #region ExecuteAndCapture tests + [Fact] + public async Task Executing_the_policy_action_successfully_should_return_success_result() + { + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => TaskHelper.EmptyTask); - [Fact] - public async Task Executing_the_policy_action_successfully_should_return_success_result() + result.Should().BeEquivalentTo(new { - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => TaskHelper.EmptyTask); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - }); - } - - [Fact] - public async Task Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() - { - var handledException = new DivideByZeroException(); - - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw handledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy, - }); - } - - [Fact] - public async Task Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); - - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw unhandledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled - }); - } - - [Fact] - public async Task Executing_the_policy_function_successfully_should_return_success_result() - { - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(Int32.MaxValue)); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(int), - Result = Int32.MaxValue - }); - } - - [Fact] - public async Task Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() - { - var handledException = new DivideByZeroException(); - - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw handledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy, - FaultType = FaultType.ExceptionHandledByThisPolicy, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - [Fact] - public async Task Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); - - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw unhandledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled, - FaultType = FaultType.UnhandledException, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - #endregion - - #region Context tests - - [Fact] - public async Task Executing_the_policy_action_should_throw_when_context_data_is_null() + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + }); + } + + [Fact] + public async Task Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() + { + var handledException = new DivideByZeroException(); + + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw handledException); + + result.Should().BeEquivalentTo(new { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy, + }); + } - await policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) - .Should().ThrowAsync(); - } + [Fact] + public async Task Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() + { + var unhandledException = new Exception(); - [Fact] - public async Task Executing_the_policy_action_should_throw_when_context_is_null() + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw unhandledException); + + result.Should().BeEquivalentTo(new { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled + }); + } - var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (Context)null)) - .Should().ThrowAsync(); - ex.And.ParamName.Should().Be("context"); - } + [Fact] + public async Task Executing_the_policy_function_successfully_should_return_success_result() + { + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(Int32.MaxValue)); - [Fact] - public async Task Executing_the_policy_function_should_throw_when_context_data_is_null() + result.Should().BeEquivalentTo(new { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(int), + Result = Int32.MaxValue + }); + } + + [Fact] + public async Task Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() + { + var handledException = new DivideByZeroException(); - await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (IDictionary)null)) - .Should().ThrowAsync(); - } + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw handledException); - [Fact] - public async Task Executing_the_policy_function_should_throw_when_context_is_null() + result.Should().BeEquivalentTo(new { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy, + FaultType = FaultType.ExceptionHandledByThisPolicy, + FinalHandledResult = default(int), + Result = default(int) + }); + } + + [Fact] + public async Task Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() + { + var unhandledException = new Exception(); - var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (Context)null)) - .Should().ThrowAsync(); - ex.And.ParamName.Should().Be("context"); - } + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw unhandledException); - [Fact] - public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() + result.Should().BeEquivalentTo(new { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled, + FaultType = FaultType.UnhandledException, + FinalHandledResult = default(int), + Result = default(int) + }); + } - var policy = Policy.NoOpAsync(); + #endregion - await policy.ExecuteAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); + #region Context tests - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public async Task Executing_the_policy_action_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public async Task Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + await policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) + .Should().ThrowAsync(); + } - await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) - .Should().ThrowAsync(); - } + [Fact] + public async Task Executing_the_policy_action_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public async Task Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); + } - var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (Context)null)) - .Should().ThrowAsync(); - ex.And.ParamName.Should().Be("context"); - } + [Fact] + public async Task Executing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (IDictionary)null)) + .Should().ThrowAsync(); + } - await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (IDictionary)null)) - .Should().ThrowAsync(); - } + [Fact] + public async Task Executing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); + } - var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (Context)null)) - .Should().ThrowAsync(); - ex.And.ParamName.Should().Be("context"); - } + [Fact] + public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + await policy.ExecuteAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); - await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public async Task Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) + .Should().ThrowAsync(); + } + + [Fact] + public async Task Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); + + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); + } - var policy = Policy.NoOpAsync(); + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - (await policy.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, executionContext)) - .Context.Should().BeSameAs(executionContext); - } + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (IDictionary)null)) + .Should().ThrowAsync(); + } + + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - #endregion + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); } + + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; + + var policy = Policy.NoOpAsync(); + + await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); + + capturedContext.Should().BeSameAs(executionContext); + } + + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + + var policy = Policy.NoOpAsync(); + + (await policy.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, executionContext)) + .Context.Should().BeSameAs(executionContext); + } + + #endregion } diff --git a/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs b/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs index a02c3b347b0..c6ada99be00 100644 --- a/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs @@ -5,325 +5,324 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class PolicyKeyAsyncSpecs { - public class PolicyKeyAsyncSpecs - { - #region Configuration + #region Configuration - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() - { - var policy = Policy.Handle().RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() + { + var policy = Policy.Handle().RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); - policy.Should().BeAssignableTo(); - } + policy.Should().BeAssignableTo(); + } - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - IAsyncPolicy policyAsInterface = Policy.Handle().RetryAsync(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + IAsyncPolicy policyAsInterface = Policy.Handle().RetryAsync(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); - } + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); + } - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - var policy = Policy.Handle().RetryAsync().WithPolicyKey(key); + var policy = Policy.Handle().RetryAsync().WithPolicyKey(key); - policy.PolicyKey.Should().Be(key); - } + policy.PolicyKey.Should().Be(key); + } - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.Handle().RetryAsync(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.Handle().RetryAsync(); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().NotThrow(); + configure.Should().NotThrow(); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.Handle().RetryAsync(); + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.Handle().RetryAsync(); - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.Handle().RetryAsync(); - - policy.PolicyKey.Should().StartWith("AsyncRetry"); - } + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.Handle().RetryAsync(); + + policy.PolicyKey.Should().StartWith("AsyncRetry"); + } - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.Handle().RetryAsync(); - var policy2 = Policy.Handle().RetryAsync(); + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.Handle().RetryAsync(); + var policy2 = Policy.Handle().RetryAsync(); - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.Handle().RetryAsync(); + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.Handle().RetryAsync(); - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.Handle().RetryAsync(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.Handle().RetryAsync(); - string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - #endregion + #endregion - #region PolicyKey and execution Context tests + #region PolicyKey and execution Context tests - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context() - { - string policyKey = Guid.NewGuid().ToString(); + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context() + { + string policyKey = Guid.NewGuid().ToString(); - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); - await retry.RaiseExceptionAsync(1); + await retry.RaiseExceptionAsync(1); - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - [Fact] - public async Task Should_pass_OperationKey_to_execution_context() - { - string operationKey = "SomeKey"; + [Fact] + public async Task Should_pass_OperationKey_to_execution_context() + { + string operationKey = "SomeKey"; - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry); + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry); - bool firstExecution = true; - await retry.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - }, new Context(operationKey)); - - operationKeySetOnContext.Should().Be(operationKey); - } - - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() + bool firstExecution = true; + await retry.ExecuteAsync(async _ => { - string policyKey = Guid.NewGuid().ToString(); + await TaskHelper.EmptyTask; + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + }, new Context(operationKey)); - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); + operationKeySetOnContext.Should().Be(operationKey); + } - bool firstExecution = true; - await retry.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - return 0; - }); - - policyKeySetOnExecutionContext.Should().Be(policyKey); - } - - [Fact] - public async Task Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() - { - string operationKey = "SomeKey"; + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() + { + string policyKey = Guid.NewGuid().ToString(); - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry); + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); - bool firstExecution = true; - await retry.ExecuteAsync(async _ => + bool firstExecution = true; + await retry.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; + if (firstExecution) { - await TaskHelper.EmptyTask; - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - return 0; - }, new Context(operationKey)); - - operationKeySetOnContext.Should().Be(operationKey); - } - #endregion + firstExecution = false; + throw new Exception(); + } + return 0; + }); + policyKeySetOnExecutionContext.Should().Be(policyKey); } - public class PolicyTResultKeyAsyncSpecs + [Fact] + public async Task Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() { - #region Configuration + string operationKey = "SomeKey"; + + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry); - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() + bool firstExecution = true; + await retry.ExecuteAsync(async _ => { - var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); + await TaskHelper.EmptyTask; + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + return 0; + }, new Context(operationKey)); - policy.Should().BeAssignableTo>(); - } + operationKeySetOnContext.Should().Be(operationKey); + } + #endregion - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - IAsyncPolicy policyAsInterface = Policy.HandleResult(0).RetryAsync(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); +} - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); - } +public class PolicyTResultKeyAsyncSpecs +{ + #region Configuration - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() + { + var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); - var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(key); + policy.Should().BeAssignableTo>(); + } - policy.PolicyKey.Should().Be(key); - } + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + IAsyncPolicy policyAsInterface = Policy.HandleResult(0).RetryAsync(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.HandleResult(0).RetryAsync(); + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); + } - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - configure.Should().NotThrow(); + var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(key); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + policy.PolicyKey.Should().Be(key); + } - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.HandleResult(0).RetryAsync(); - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).RetryAsync(); + configure.Should().NotThrow(); - policy.PolicyKey.Should().StartWith("AsyncRetry"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.HandleResult(0).RetryAsync(); - var policy2 = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).RetryAsync(); - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).RetryAsync(); - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + policy.PolicyKey.Should().StartWith("AsyncRetry"); + } - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.HandleResult(0).RetryAsync(); + var policy2 = Policy.HandleResult(0).RetryAsync(); - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.HandleResult(0).RetryAsync(); + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).RetryAsync(); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - #endregion + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.HandleResult(0).RetryAsync(); - #region PolicyKey and execution Context tests + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context() - { - string policyKey = Guid.NewGuid().ToString(); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - string policyKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(policyKey); + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - await retry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + #endregion - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + #region PolicyKey and execution Context tests - [Fact] - public async Task Should_pass_OperationKey_to_execution_context() - { - string operationKey = "SomeKey"; + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context() + { + string policyKey = Guid.NewGuid().ToString(); - string operationKeySetOnContext = null; - Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry); + string policyKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(policyKey); - bool firstExecution = true; - await retry.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - if (firstExecution) - { - firstExecution = false; - return ResultPrimitive.Fault; - } - return ResultPrimitive.Good; - }, new Context(operationKey)); + await retry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + + policyKeySetOnExecutionContext.Should().Be(policyKey); + } + + [Fact] + public async Task Should_pass_OperationKey_to_execution_context() + { + string operationKey = "SomeKey"; - operationKeySetOnContext.Should().Be(operationKey); - } + string operationKeySetOnContext = null; + Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry); - #endregion + bool firstExecution = true; + await retry.ExecuteAsync(async _ => + { + await TaskHelper.EmptyTask; + if (firstExecution) + { + firstExecution = false; + return ResultPrimitive.Fault; + } + return ResultPrimitive.Good; + }, new Context(operationKey)); + operationKeySetOnContext.Should().Be(operationKey); } + + #endregion + } diff --git a/src/Polly.Specs/PolicyContextAndKeySpecs.cs b/src/Polly.Specs/PolicyContextAndKeySpecs.cs index d35ef09807f..2f6ce29057f 100644 --- a/src/Polly.Specs/PolicyContextAndKeySpecs.cs +++ b/src/Polly.Specs/PolicyContextAndKeySpecs.cs @@ -3,322 +3,321 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class PolicyKeySpecs { - public class PolicyKeySpecs + #region Configuration + + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() { - #region Configuration + var policy = Policy.Handle().Retry().WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() - { - var policy = Policy.Handle().Retry().WithPolicyKey(Guid.NewGuid().ToString()); + policy.Should().BeAssignableTo(); + } - policy.Should().BeAssignableTo(); - } + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + ISyncPolicy policyAsInterface = Policy.Handle().Retry(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - ISyncPolicy policyAsInterface = Policy.Handle().Retry(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); + } - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); - } + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + var policy = Policy.Handle().Retry().WithPolicyKey(key); - var policy = Policy.Handle().Retry().WithPolicyKey(key); + policy.PolicyKey.Should().Be(key); + } - policy.PolicyKey.Should().Be(key); - } + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.Handle().Retry(); - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.Handle().Retry(); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + configure.Should().NotThrow(); - configure.Should().NotThrow(); + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.Handle().Retry(); - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.Handle().Retry(); + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.Handle().Retry(); - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.Handle().Retry(); + policy.PolicyKey.Should().StartWith("Retry"); + } - policy.PolicyKey.Should().StartWith("Retry"); - } + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.Handle().Retry(); + var policy2 = Policy.Handle().Retry(); - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.Handle().Retry(); - var policy2 = Policy.Handle().Retry(); + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.Handle().Retry(); - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.Handle().Retry(); + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.Handle().Retry(); - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.Handle().Retry(); + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + #endregion - #endregion + #region PolicyKey and execution Context tests - #region PolicyKey and execution Context tests + [Fact] + public void Should_pass_PolicyKey_to_execution_context() + { + string policyKey = Guid.NewGuid().ToString(); - [Fact] - public void Should_pass_PolicyKey_to_execution_context() - { - string policyKey = Guid.NewGuid().ToString(); + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); + retry.RaiseException(1); - retry.RaiseException(1); + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + [Fact] + public void Should_pass_OperationKey_to_execution_context() + { + string operationKey = "SomeKey"; - [Fact] - public void Should_pass_OperationKey_to_execution_context() + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().Retry(1, onRetry); + + bool firstExecution = true; + retry.Execute(_ => { - string operationKey = "SomeKey"; + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + }, new Context(operationKey)); - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().Retry(1, onRetry); + operationKeySetOnContext.Should().Be(operationKey); + } - bool firstExecution = true; - retry.Execute(_ => - { - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - }, new Context(operationKey)); - - operationKeySetOnContext.Should().Be(operationKey); - } - - [Fact] - public void Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() - { - string policyKey = Guid.NewGuid().ToString(); + [Fact] + public void Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() + { + string policyKey = Guid.NewGuid().ToString(); - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); - bool firstExecution = true; - retry.Execute(() => - { - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - return 0; - }); - - policyKeySetOnExecutionContext.Should().Be(policyKey); - } - - [Fact] - public void Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() + bool firstExecution = true; + retry.Execute(() => { - string operationKey = "SomeKey"; + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + return 0; + }); + + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().Retry(1, onRetry); + [Fact] + public void Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() + { + string operationKey = "SomeKey"; + + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().Retry(1, onRetry); - bool firstExecution = true; - retry.Execute(_ => + bool firstExecution = true; + retry.Execute(_ => + { + if (firstExecution) { - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - return 0; - }, new Context(operationKey)); - - operationKeySetOnContext.Should().Be(operationKey); - } - #endregion + firstExecution = false; + throw new Exception(); + } + return 0; + }, new Context(operationKey)); + + operationKeySetOnContext.Should().Be(operationKey); } + #endregion +} - public class PolicyTResultKeySpecs - { - #region Configuration +public class PolicyTResultKeySpecs +{ + #region Configuration - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() - { - var policy = Policy.HandleResult(0).Retry().WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() + { + var policy = Policy.HandleResult(0).Retry().WithPolicyKey(Guid.NewGuid().ToString()); - policy.Should().BeAssignableTo>(); - } + policy.Should().BeAssignableTo>(); + } - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - ISyncPolicy policyAsInterface = Policy.HandleResult(0).Retry(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + ISyncPolicy policyAsInterface = Policy.HandleResult(0).Retry(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); - } + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); + } - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - var policy = Policy.HandleResult(0).Retry().WithPolicyKey(key); + var policy = Policy.HandleResult(0).Retry().WithPolicyKey(key); - policy.PolicyKey.Should().Be(key); - } + policy.PolicyKey.Should().Be(key); + } - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.HandleResult(0).Retry(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.HandleResult(0).Retry(); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().NotThrow(); + configure.Should().NotThrow(); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).Retry(); + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).Retry(); - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).Retry(); + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).Retry(); - policy.PolicyKey.Should().StartWith("Retry"); - } + policy.PolicyKey.Should().StartWith("Retry"); + } - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.HandleResult(0).Retry(); - var policy2 = Policy.HandleResult(0).Retry(); + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.HandleResult(0).Retry(); + var policy2 = Policy.HandleResult(0).Retry(); - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).Retry(); + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).Retry(); - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.HandleResult(0).Retry(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.HandleResult(0).Retry(); - string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - #endregion + #endregion - #region PolicyKey and execution Context tests + #region PolicyKey and execution Context tests - [Fact] - public void Should_pass_PolicyKey_to_execution_context() - { - string policyKey = Guid.NewGuid().ToString(); + [Fact] + public void Should_pass_PolicyKey_to_execution_context() + { + string policyKey = Guid.NewGuid().ToString(); - string policyKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(policyKey); + string policyKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(policyKey); - retry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + retry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - [Fact] - public void Should_pass_OperationKey_to_execution_context() - { - string operationKey = "SomeKey"; + [Fact] + public void Should_pass_OperationKey_to_execution_context() + { + string operationKey = "SomeKey"; - string operationKeySetOnContext = null; - Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry); + string operationKeySetOnContext = null; + Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry); - bool firstExecution = true; - retry.Execute(_ => + bool firstExecution = true; + retry.Execute(_ => + { + if (firstExecution) { - if (firstExecution) - { - firstExecution = false; - return ResultPrimitive.Fault; - } - return ResultPrimitive.Good; - }, new Context(operationKey)); - - operationKeySetOnContext.Should().Be(operationKey); - } - - #endregion + firstExecution = false; + return ResultPrimitive.Fault; + } + return ResultPrimitive.Good; + }, new Context(operationKey)); + + operationKeySetOnContext.Should().Be(operationKey); } + + #endregion } diff --git a/src/Polly.Specs/PolicySpecs.cs b/src/Polly.Specs/PolicySpecs.cs index 4554cbf9f5a..0d335066ea7 100644 --- a/src/Polly.Specs/PolicySpecs.cs +++ b/src/Polly.Specs/PolicySpecs.cs @@ -3,293 +3,292 @@ using FluentAssertions; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class PolicySpecs { - public class PolicySpecs + #region Execute tests + + [Fact] + public void Executing_the_policy_action_should_execute_the_specified_action() { - #region Execute tests + var executed = false; - [Fact] - public void Executing_the_policy_action_should_execute_the_specified_action() - { - var executed = false; + var policy = Policy + .Handle() + .Retry((_, _) => { }); - var policy = Policy - .Handle() - .Retry((_, _) => { }); + policy.Execute(() => executed = true); - policy.Execute(() => executed = true); + executed.Should() + .BeTrue(); + } - executed.Should() - .BeTrue(); - } + [Fact] + public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() + { + var policy = Policy + .Handle() + .Retry((_, _) => { }); - [Fact] - public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() - { - var policy = Policy - .Handle() - .Retry((_, _) => { }); + var result = policy.Execute(() => 2); - var result = policy.Execute(() => 2); + result.Should() + .Be(2); + } - result.Should() - .Be(2); - } + #endregion - #endregion + #region ExecuteAndCapture tests - #region ExecuteAndCapture tests + [Fact] + public void Executing_the_policy_action_successfully_should_return_success_result() + { + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => { }); - [Fact] - public void Executing_the_policy_action_successfully_should_return_success_result() - { - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => { }); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception) null, - ExceptionType = (ExceptionType?) null, - }); - } - - [Fact] - public void Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() + result.Should().BeEquivalentTo(new { - var handledException = new DivideByZeroException(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw handledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy - }); - } - - [Fact] - public void Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw unhandledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled - }); - } - - [Fact] - public void Executing_the_policy_function_successfully_should_return_success_result() - { - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => Int32.MaxValue); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(int), - Result = Int32.MaxValue - }); - } - - [Fact] - public void Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() - { - var handledException = new DivideByZeroException(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw handledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy, - FaultType = FaultType.ExceptionHandledByThisPolicy, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - [Fact] - public void Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw unhandledException); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled, - FaultType = FaultType.UnhandledException, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - #endregion - - #region Context tests - - [Fact] - public void Executing_the_policy_action_should_throw_when_context_data_is_null() + Outcome = OutcomeType.Successful, + FinalException = (Exception) null, + ExceptionType = (ExceptionType?) null, + }); + } + + [Fact] + public void Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() + { + var handledException = new DivideByZeroException(); + + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw handledException); + + result.Should().BeEquivalentTo(new { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy + }); + } - policy.Invoking(p => p.Execute(_ => { }, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() + { + var unhandledException = new Exception(); - [Fact] - public void Executing_the_policy_action_should_throw_when_context_is_null() + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw unhandledException); + + result.Should().BeEquivalentTo(new { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled + }); + } - policy.Invoking(p => p.Execute(_ => { }, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Executing_the_policy_function_successfully_should_return_success_result() + { + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => Int32.MaxValue); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() + result.Should().BeEquivalentTo(new { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(int), + Result = Int32.MaxValue + }); + } + + [Fact] + public void Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() + { + var handledException = new DivideByZeroException(); - policy.Invoking(p => p.Execute(_ => 2, (IDictionary)null)) - .Should().Throw(); - } + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw handledException); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() + result.Should().BeEquivalentTo(new { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy, + FaultType = FaultType.ExceptionHandledByThisPolicy, + FinalHandledResult = default(int), + Result = default(int) + }); + } + + [Fact] + public void Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() + { + var unhandledException = new Exception(); - policy.Invoking(p => p.Execute(_ => 2, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw unhandledException); - [Fact] - public void Executing_the_policy_function_should_pass_context_to_executed_delegate() + result.Should().BeEquivalentTo(new { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled, + FaultType = FaultType.UnhandledException, + FinalHandledResult = default(int), + Result = default(int) + }); + } - Policy policy = Policy.NoOp(); + #endregion - policy.Execute(context => { capturedContext = context; }, executionContext); + #region Context tests - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Executing_the_policy_action_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + policy.Invoking(p => p.Execute(_ => { }, (IDictionary)null)) + .Should().Throw(); + } + + [Fact] + public void Executing_the_policy_action_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (IDictionary)null)) - .Should().Throw(); - } + policy.Invoking(p => p.Execute(_ => { }, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + [Fact] + public void Executing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + policy.Invoking(p => p.Execute(_ => 2, (IDictionary)null)) + .Should().Throw(); + } - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + [Fact] + public void Executing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (IDictionary)null)) - .Should().Throw(); - } + policy.Invoking(p => p.Execute(_ => 2, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + [Fact] + public void Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; - policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + Policy policy = Policy.NoOp(); - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + policy.Execute(context => { capturedContext = context; }, executionContext); + + capturedContext.Should().BeSameAs(executionContext); + } - Policy policy = Policy.NoOp(); + [Fact] + public void Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - policy.ExecuteAndCapture(context => { capturedContext = context; }, executionContext); + policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (IDictionary)null)) + .Should().Throw(); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); + policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - Policy policy = Policy.NoOp(); + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - policy.ExecuteAndCapture(_ => { }, executionContext) - .Context.Should().BeSameAs(executionContext); - } + policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (IDictionary)null)) + .Should().Throw(); + } + + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - #endregion + policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); } -} \ No newline at end of file + + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; + + Policy policy = Policy.NoOp(); + + policy.ExecuteAndCapture(context => { capturedContext = context; }, executionContext); + + capturedContext.Should().BeSameAs(executionContext); + } + + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + + Policy policy = Policy.NoOp(); + + policy.ExecuteAndCapture(_ => { }, executionContext) + .Context.Should().BeSameAs(executionContext); + } + + #endregion +} diff --git a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs index d9638ef5ca3..f767dbb5e6e 100644 --- a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs @@ -5,183 +5,182 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class PolicyTResultAsyncSpecs { - public class PolicyTResultAsyncSpecs + #region Execute tests + + [Fact] + public async Task Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() { - #region Execute tests + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _) => { }); - [Fact] - public async Task Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _) => { }); + var result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - var result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + result.Should() + .Be(ResultPrimitive.Good); + } - result.Should() - .Be(ResultPrimitive.Good); - } + #endregion - #endregion + #region ExecuteAndCapture tests - #region ExecuteAndCapture tests + [Fact] + public async Task Executing_the_policy_function_successfully_should_return_success_result() + { + var result = await Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Good)); - [Fact] - public async Task Executing_the_policy_function_successfully_should_return_success_result() + result.Should().BeEquivalentTo(new { - var result = await Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Good)); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = ResultPrimitive.Good, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } - - [Fact] - public async Task Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() - { - var handledResult = ResultPrimitive.Fault; - - var result = await Policy - .HandleResult(handledResult) - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(handledResult)); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = FaultType.ResultHandledByThisPolicy, - FinalHandledResult = handledResult, - Result = default(ResultPrimitive) - }); - } - - [Fact] - public async Task Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() - { - var handledResult = ResultPrimitive.Fault; - var unhandledResult = ResultPrimitive.Good; + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = ResultPrimitive.Good, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } - var result = await Policy - .HandleResult(handledResult) - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(unhandledResult)); + [Fact] + public async Task Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() + { + var handledResult = ResultPrimitive.Fault; - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = unhandledResult, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } + var result = await Policy + .HandleResult(handledResult) + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(handledResult)); - #endregion + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = FaultType.ResultHandledByThisPolicy, + FinalHandledResult = handledResult, + Result = default(ResultPrimitive) + }); + } - #region Context tests + [Fact] + public async Task Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() + { + var handledResult = ResultPrimitive.Fault; + var unhandledResult = ResultPrimitive.Good; + var result = await Policy + .HandleResult(handledResult) + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(unhandledResult)); - [Fact] - public async Task Executing_the_policy_function_should_throw_when_context_data_is_null() + result.Should().BeEquivalentTo(new { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = unhandledResult, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } - await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (IDictionary)null)) - .Should().ThrowAsync(); - } + #endregion - [Fact] - public async Task Executing_the_policy_function_should_throw_when_context_is_null() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + #region Context tests - var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().ThrowAsync(); - ex.And.ParamName.Should().Be("context"); - } - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + [Fact] + public async Task Executing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().ThrowAsync(); - ex.And.ParamName.Should().Be("context"); - } + await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (IDictionary)null)) + .Should().ThrowAsync(); + } - [Fact] - public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + [Fact] + public async Task Executing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - var policy = Policy.NoOpAsync(); + var ex = await policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); + } - await policy.ExecuteAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - capturedContext.Should().BeSameAs(executionContext); - } + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); + } - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + [Fact] + public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; - var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().ThrowAsync(); - ex.And.ParamName.Should().Be("context"); - } + var policy = Policy.NoOpAsync(); - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + await policy.ExecuteAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); + + capturedContext.Should().BeSameAs(executionContext); + } - var policy = Policy.NoOpAsync(); + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); + var ex = await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().ThrowAsync(); + ex.And.ParamName.Should().Be("context"); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); - (await policy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), executionContext)) - .Context.Should().BeSameAs(executionContext); - } + capturedContext.Should().BeSameAs(executionContext); + } - #endregion + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + + var policy = Policy.NoOpAsync(); + + (await policy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), executionContext)) + .Context.Should().BeSameAs(executionContext); } -} \ No newline at end of file + + #endregion +} diff --git a/src/Polly.Specs/PolicyTResultSpecs.cs b/src/Polly.Specs/PolicyTResultSpecs.cs index f7fb42d2741..9720c752c7e 100644 --- a/src/Polly.Specs/PolicyTResultSpecs.cs +++ b/src/Polly.Specs/PolicyTResultSpecs.cs @@ -4,181 +4,180 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs +namespace Polly.Specs; + +public class PolicyTResultSpecs { - public class PolicyTResultSpecs + #region Execute tests + + [Fact] + public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() { - #region Execute tests + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _) => { }); - [Fact] - public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _) => { }); + var result = policy.Execute(() => ResultPrimitive.Good); - var result = policy.Execute(() => ResultPrimitive.Good); + result.Should() + .Be(ResultPrimitive.Good); + } - result.Should() - .Be(ResultPrimitive.Good); - } + #endregion - #endregion + #region ExecuteAndCapture tests - #region ExecuteAndCapture tests + [Fact] + public void Executing_the_policy_function_successfully_should_return_success_result() + { + var result = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _) => { }) + .ExecuteAndCapture(() => ResultPrimitive.Good); - [Fact] - public void Executing_the_policy_function_successfully_should_return_success_result() + result.Should().BeEquivalentTo(new { - var result = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _) => { }) - .ExecuteAndCapture(() => ResultPrimitive.Good); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = ResultPrimitive.Good, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } - - [Fact] - public void Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() - { - var handledResult = ResultPrimitive.Fault; - - var result = Policy - .HandleResult(handledResult) - .Retry((_, _) => { }) - .ExecuteAndCapture(() => handledResult); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = FaultType.ResultHandledByThisPolicy, - FinalHandledResult = handledResult, - Result = default(ResultPrimitive) - }); - } - - [Fact] - public void Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() - { - var handledResult = ResultPrimitive.Fault; - var unhandledResult = ResultPrimitive.Good; - - var result = Policy - .HandleResult(handledResult) - .Retry((_, _) => { }) - .ExecuteAndCapture(() => unhandledResult); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = unhandledResult, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } - - #endregion - - #region Context tests - - [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = ResultPrimitive.Good, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } + + [Fact] + public void Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() + { + var handledResult = ResultPrimitive.Fault; - policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (IDictionary)null)) - .Should().Throw(); - } + var result = Policy + .HandleResult(handledResult) + .Retry((_, _) => { }) + .ExecuteAndCapture(() => handledResult); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() + result.Should().BeEquivalentTo(new { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + Outcome = OutcomeType.Failure, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = FaultType.ResultHandledByThisPolicy, + FinalHandledResult = handledResult, + Result = default(ResultPrimitive) + }); + } - policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() + { + var handledResult = ResultPrimitive.Fault; + var unhandledResult = ResultPrimitive.Good; - [Fact] - public void Executing_the_policy_function_should_pass_context_to_executed_delegate() + var result = Policy + .HandleResult(handledResult) + .Retry((_, _) => { }) + .ExecuteAndCapture(() => unhandledResult); + + result.Should().BeEquivalentTo(new { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = unhandledResult, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } - Policy policy = Policy.NoOp(); + #endregion - policy.Execute(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); + #region Context tests - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (IDictionary)null)) + .Should().Throw(); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); - Context capturedContext = null; + Policy policy = Policy.NoOp(); - Policy policy = Policy.NoOp(); + policy.Execute(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); - policy.ExecuteAndCapture(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - string operationKey = "SomeKey"; - Context executionContext = new Context(operationKey); + policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (IDictionary)null)) + .Should().Throw(); + } - Policy policy = Policy.NoOp(); + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - policy.ExecuteAndCapture(_ => ResultPrimitive.Good, executionContext) - .Context.Should().BeSameAs(executionContext); - } + policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - #endregion + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + Context capturedContext = null; + + Policy policy = Policy.NoOp(); + + policy.ExecuteAndCapture(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); + + capturedContext.Should().BeSameAs(executionContext); } -} \ No newline at end of file + + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); + + Policy policy = Policy.NoOp(); + + policy.ExecuteAndCapture(_ => ResultPrimitive.Good, executionContext) + .Context.Should().BeSameAs(executionContext); + } + + #endregion +} diff --git a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs index 24b70fa2e50..addb68a7eff 100644 --- a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs +++ b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs @@ -6,44 +6,43 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] +public class AsyncRateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable { - [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] - public class AsyncRateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable + public void Dispose() { - public void Dispose() - { - SystemClock.Reset(); - } + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); - } - - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + } + + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + { + if (policy is AsyncRateLimitPolicy typedPolicy) { - if (policy is AsyncRateLimitPolicy typedPolicy) + try { - try - { - typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); - return (true, TimeSpan.Zero); - } - catch (RateLimitRejectedException e) - { - return (false, e.RetryAfter); - } + typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); + return (true, TimeSpan.Zero); } - else + catch (RateLimitRejectedException e) { - throw new InvalidOperationException("Unexpected policy type in test construction."); + return (false, e.RetryAfter); } } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); + } } } diff --git a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs index 19073d3f3ed..fc12dab463b 100644 --- a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs +++ b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs @@ -6,62 +6,61 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] +public class AsyncRateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable { - [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] - public class AsyncRateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable + public void Dispose() { - public void Dispose() - { - SystemClock.Reset(); - } + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, - Func retryAfterFactory) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, + Func retryAfterFactory) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + } - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + { + if (policy is AsyncRateLimitPolicy typedPolicy) { - if (policy is AsyncRateLimitPolicy typedPolicy) + try { - try - { - typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); - return (true, TimeSpan.Zero); - } - catch (RateLimitRejectedException e) - { - return (false, e.RetryAfter); - } + typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); + return (true, TimeSpan.Zero); } - else + catch (RateLimitRejectedException e) { - throw new InvalidOperationException("Unexpected policy type in test construction."); + return (false, e.RetryAfter); } } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); + } + } - protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) + protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) + { + if (policy is AsyncRateLimitPolicy typedPolicy) { - if (policy is AsyncRateLimitPolicy typedPolicy) - { - return typedPolicy.ExecuteAsync(ctx => Task.FromResult(resultIfExecutionPermitted), context).GetAwaiter().GetResult(); - } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); - } + return typedPolicy.ExecuteAsync(ctx => Task.FromResult(resultIfExecutionPermitted), context).GetAwaiter().GetResult(); + } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); } } } diff --git a/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs b/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs index 31376594f63..8f6f122fd8c 100644 --- a/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs +++ b/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs @@ -1,11 +1,10 @@ using System; using Polly.RateLimit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +public class LockFreeTokenBucketRateLimiterTests : TokenBucketRateLimiterTestsBase { - public class LockFreeTokenBucketRateLimiterTests : TokenBucketRateLimiterTestsBase - { - internal override IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity) - => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); - } + internal override IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity) + => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); } diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs b/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs index dec7aa35f0d..7994baaec7f 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs @@ -5,44 +5,43 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] +public class RateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable { - [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] - public class RateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable + public void Dispose() { - public void Dispose() - { - SystemClock.Reset(); - } + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + } - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + { + if (policy is RateLimitPolicy typedPolicy) { - if (policy is RateLimitPolicy typedPolicy) + try { - try - { - typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); - return (true, TimeSpan.Zero); - } - catch (RateLimitRejectedException e) - { - return (false, e.RetryAfter); - } + typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); + return (true, TimeSpan.Zero); } - else + catch (RateLimitRejectedException e) { - throw new InvalidOperationException("Unexpected policy type in test construction."); + return (false, e.RetryAfter); } } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs index fb7d4fab8d9..6bc1dbd14a1 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs @@ -6,308 +6,307 @@ using Polly.RateLimit; using Xunit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +public abstract class RateLimitPolicySpecsBase : RateLimitSpecsBase { - public abstract class RateLimitPolicySpecsBase : RateLimitSpecsBase - { - protected abstract IRateLimitPolicy GetPolicyViaSyntax( - int numberOfExecutions, - TimeSpan perTimeSpan); + protected abstract IRateLimitPolicy GetPolicyViaSyntax( + int numberOfExecutions, + TimeSpan perTimeSpan); - protected abstract IRateLimitPolicy GetPolicyViaSyntax( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst); + protected abstract IRateLimitPolicy GetPolicyViaSyntax( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst); - protected abstract (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy); + protected abstract (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy); - protected void ShouldPermitAnExecution(IRateLimitPolicy policy) - { - (bool permitExecution, TimeSpan retryAfter) = TryExecuteThroughPolicy(policy); + protected void ShouldPermitAnExecution(IRateLimitPolicy policy) + { + (bool permitExecution, TimeSpan retryAfter) = TryExecuteThroughPolicy(policy); - permitExecution.Should().BeTrue(); - retryAfter.Should().Be(TimeSpan.Zero); - } + permitExecution.Should().BeTrue(); + retryAfter.Should().Be(TimeSpan.Zero); + } - protected void ShouldPermitNExecutions(IRateLimitPolicy policy, long numberOfExecutions) + protected void ShouldPermitNExecutions(IRateLimitPolicy policy, long numberOfExecutions) + { + for (int execution = 0; execution < numberOfExecutions; execution++) { - for (int execution = 0; execution < numberOfExecutions; execution++) - { - ShouldPermitAnExecution(policy); - } + ShouldPermitAnExecution(policy); } + } - protected void ShouldNotPermitAnExecution(IRateLimitPolicy policy, TimeSpan? retryAfter = null) - { - (bool permitExecution, TimeSpan retryAfter) canExecute = TryExecuteThroughPolicy(policy); - - canExecute.permitExecution.Should().BeFalse(); - if (retryAfter == null) - { - canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); - } - else - { - canExecute.retryAfter.Should().Be(retryAfter.Value); - } - } + protected void ShouldNotPermitAnExecution(IRateLimitPolicy policy, TimeSpan? retryAfter = null) + { + (bool permitExecution, TimeSpan retryAfter) canExecute = TryExecuteThroughPolicy(policy); - [Fact] - public void Syntax_should_throw_for_perTimeSpan_zero() + canExecute.permitExecution.Should().BeFalse(); + if (retryAfter == null) { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.Zero); - - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); } - - [Fact] - public void Syntax_should_throw_for_perTimeSpan_infinite() + else { - Action invalidSyntax = () => GetPolicyViaSyntax(1, System.Threading.Timeout.InfiniteTimeSpan); - - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + canExecute.retryAfter.Should().Be(retryAfter.Value); } + } - [Fact] - public void Syntax_should_throw_for_perTimeSpan_too_small() - { - Action invalidSyntax = () => GetPolicyViaSyntax(int.MaxValue, TimeSpan.FromSeconds(1)); - - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); - invalidSyntax.Should().Throw().And.Message.Should().StartWith("The number of executions per timespan must be positive."); - } + [Fact] + public void Syntax_should_throw_for_perTimeSpan_zero() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.Zero); - [Fact] - public void Syntax_should_throw_for_numberOfExecutions_negative() - { - Action invalidSyntax = () => GetPolicyViaSyntax(-1, TimeSpan.FromSeconds(1)); + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + } - invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); - } + [Fact] + public void Syntax_should_throw_for_perTimeSpan_infinite() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, System.Threading.Timeout.InfiniteTimeSpan); - [Fact] - public void Syntax_should_throw_for_numberOfExecutions_zero() - { - Action invalidSyntax = () => GetPolicyViaSyntax(0, TimeSpan.FromSeconds(1)); + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + } - invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); - } + [Fact] + public void Syntax_should_throw_for_perTimeSpan_too_small() + { + Action invalidSyntax = () => GetPolicyViaSyntax(int.MaxValue, TimeSpan.FromSeconds(1)); - [Fact] - public void Syntax_should_throw_for_perTimeSpan_negative() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromTicks(-1)); + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + invalidSyntax.Should().Throw().And.Message.Should().StartWith("The number of executions per timespan must be positive."); + } - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); - } + [Fact] + public void Syntax_should_throw_for_numberOfExecutions_negative() + { + Action invalidSyntax = () => GetPolicyViaSyntax(-1, TimeSpan.FromSeconds(1)); - [Fact] - public void Syntax_should_throw_for_maxBurst_negative() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), -1); + invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); + } - invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); - } + [Fact] + public void Syntax_should_throw_for_numberOfExecutions_zero() + { + Action invalidSyntax = () => GetPolicyViaSyntax(0, TimeSpan.FromSeconds(1)); - [Fact] - public void Syntax_should_throw_for_maxBurst_zero() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), 0); + invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); + } - invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); - } + [Fact] + public void Syntax_should_throw_for_perTimeSpan_negative() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromTicks(-1)); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) - { - FixClock(); + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + } - // Arrange - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer); + [Fact] + public void Syntax_should_throw_for_maxBurst_negative() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), -1); - // Assert - first execution after initialising should always be permitted. - ShouldPermitAnExecution(rateLimiter); + invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); + } - // Arrange - // (do nothing - time not advanced) + [Fact] + public void Syntax_should_throw_for_maxBurst_zero() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), 0); - // Assert - should be blocked - time not advanced. - ShouldNotPermitAnExecution(rateLimiter, onePer); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(50)] - public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) + { + FixClock(); - // Arrange. - TimeSpan onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer); - // Act - should be able to successfully take bucketCapacity items. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); + // Assert - first execution after initialising should always be permitted. + ShouldPermitAnExecution(rateLimiter); - // Assert - should not be able to take any items (given time not advanced). - ShouldNotPermitAnExecution(rateLimiter, onePer); - } + // Arrange + // (do nothing - time not advanced) - [Theory] - [InlineData(1, 1)] - [InlineData(2, 1)] - [InlineData(5, 1)] - [InlineData(1, 10)] - [InlineData(2, 10)] - [InlineData(5, 10)] - public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) - { - FixClock(); + // Assert - should be blocked - time not advanced. + ShouldNotPermitAnExecution(rateLimiter, onePer); + } - // Arrange - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(50)] + public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) + { + FixClock(); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange. + TimeSpan onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval - int experimentRepeats = bucketCapacity * 3; - TimeSpan shortfallFromInterval = TimeSpan.FromTicks(1); - TimeSpan notQuiteInterval = onePer - shortfallFromInterval; - for (int i = 0; i < experimentRepeats; i++) - { - // Arrange - Advance clock not quite to the interval - AdvanceClock(notQuiteInterval.Ticks); + // Act - should be able to successfully take bucketCapacity items. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); - // Assert - should not quite be able to issue another token - ShouldNotPermitAnExecution(rateLimiter, shortfallFromInterval); + // Assert - should not be able to take any items (given time not advanced). + ShouldNotPermitAnExecution(rateLimiter, onePer); + } - // Arrange - Advance clock to the interval - AdvanceClock(shortfallFromInterval.Ticks); + [Theory] + [InlineData(1, 1)] + [InlineData(2, 1)] + [InlineData(5, 1)] + [InlineData(1, 10)] + [InlineData(2, 10)] + [InlineData(5, 10)] + public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) + { + FixClock(); - // Act - ShouldPermitAnExecution(rateLimiter); + // Arrange + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Assert - but cannot get another token straight away - ShouldNotPermitAnExecution(rateLimiter); - } - } + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) + // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval + int experimentRepeats = bucketCapacity * 3; + TimeSpan shortfallFromInterval = TimeSpan.FromTicks(1); + TimeSpan notQuiteInterval = onePer - shortfallFromInterval; + for (int i = 0; i < experimentRepeats; i++) { - FixClock(); + // Arrange - Advance clock not quite to the interval + AdvanceClock(notQuiteInterval.Ticks); - // Arrange - int onePerSeconds = 1; - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Assert - should not quite be able to issue another token + ShouldNotPermitAnExecution(rateLimiter, shortfallFromInterval); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange - Advance clock to the interval + AdvanceClock(shortfallFromInterval.Ticks); - // Arrange - advance exactly enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity); + // Act + ShouldPermitAnExecution(rateLimiter); - // Assert - expect full bucket capacity but no more - ShouldPermitNExecutions(rateLimiter, bucketCapacity); + // Assert - but cannot get another token straight away ShouldNotPermitAnExecution(rateLimiter); } + } - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) - { - (bucketCapacity % 2).Should().Be(0); + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) + { + FixClock(); - FixClock(); + // Arrange + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Arrange - int onePerSeconds = 1; - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange - advance exactly enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); + // Assert - expect full bucket capacity but no more + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); + } - // Assert - expect full bucket capacity but no more - ShouldPermitNExecutions(rateLimiter, bucketCapacity / 2); - ShouldNotPermitAnExecution(rateLimiter); - } + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) + { + (bucketCapacity % 2).Should().Be(0); - [Theory] - [InlineData(100, 2)] - [InlineData(100, 5)] - public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) - { - multipleRefillTimePassed.Should().BeGreaterThan(1); + FixClock(); - FixClock(); + // Arrange + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Arrange - int onePerSeconds = 1; - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); + // Assert - expect full bucket capacity but no more + ShouldPermitNExecutions(rateLimiter, bucketCapacity / 2); + ShouldNotPermitAnExecution(rateLimiter); + } - // Assert - expect full bucket capacity but no more - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); - } + [Theory] + [InlineData(100, 2)] + [InlineData(100, 5)] + public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) + { + multipleRefillTimePassed.Should().BeGreaterThan(1); - [Theory] - [InlineData(2)] - [InlineData(5)] - [InlineData(100)] - public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) - { - FixClock(); + FixClock(); + + // Arrange + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - // Arrange - TimeSpan onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetPolicyViaSyntax(1, onePer); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); - // Arrange - parallel tasks all waiting on a manual reset event. - ManualResetEventSlim gate = new(); - Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; - for (int i = 0; i < parallelContention; i++) + // Assert - expect full bucket capacity but no more + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); + } + + [Theory] + [InlineData(2)] + [InlineData(5)] + [InlineData(100)] + public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) + { + FixClock(); + + // Arrange + TimeSpan onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetPolicyViaSyntax(1, onePer); + + // Arrange - parallel tasks all waiting on a manual reset event. + ManualResetEventSlim gate = new(); + Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; + for (int i = 0; i < parallelContention; i++) + { + tasks[i] = Task.Run(() => { - tasks[i] = Task.Run(() => - { - gate.Wait(); - return TryExecuteThroughPolicy(rateLimiter); - }); - } - - // Act - release gate. - gate.Set(); - Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); - - // Assert - one should have permitted execution, n-1 not. - var results = tasks.Select(t => t.Result).ToList(); - results.Count(r => r.permitExecution).Should().Be(1); - results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); + gate.Wait(); + return TryExecuteThroughPolicy(rateLimiter); + }); } + + // Act - release gate. + gate.Set(); + Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); + + // Assert - one should have permitted execution, n-1 not. + var results = tasks.Select(t => t.Result).ToList(); + results.Count(r => r.permitExecution).Should().Be(1); + results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); } } diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs index e67df0c2fca..320e13e65f6 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs @@ -5,62 +5,61 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] +public class RateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable { - [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] - public class RateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable + public void Dispose() { - public void Dispose() - { - SystemClock.Reset(); - } + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, - Func retryAfterFactory) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, + Func retryAfterFactory) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + } - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) + { + if (policy is RateLimitPolicy typedPolicy) { - if (policy is RateLimitPolicy typedPolicy) + try { - try - { - typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); - return (true, TimeSpan.Zero); - } - catch (RateLimitRejectedException e) - { - return (false, e.RetryAfter); - } + typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); + return (true, TimeSpan.Zero); } - else + catch (RateLimitRejectedException e) { - throw new InvalidOperationException("Unexpected policy type in test construction."); + return (false, e.RetryAfter); } } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); + } + } - protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) + protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) + { + if (policy is RateLimitPolicy typedPolicy) { - if (policy is RateLimitPolicy typedPolicy) - { - return typedPolicy.Execute(ctx => resultIfExecutionPermitted, context); - } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); - } + return typedPolicy.Execute(ctx => resultIfExecutionPermitted, context); + } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); } } } diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs index 3fc16eb21ef..4c1cfd45f3a 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs @@ -5,53 +5,52 @@ using Polly.Specs.Helpers.RateLimit; using Xunit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +public abstract class RateLimitPolicyTResultSpecsBase : RateLimitPolicySpecsBase { - public abstract class RateLimitPolicyTResultSpecsBase : RateLimitPolicySpecsBase + protected abstract IRateLimitPolicy GetPolicyViaSyntax( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst, + Func retryAfterFactory); + + protected abstract TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted); + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public void Ratelimiter_specifies_correct_wait_until_next_execution_by_custom_factory_passing_correct_context(int onePerSeconds) { - protected abstract IRateLimitPolicy GetPolicyViaSyntax( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst, - Func retryAfterFactory); - - protected abstract TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted); - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - public void Ratelimiter_specifies_correct_wait_until_next_execution_by_custom_factory_passing_correct_context(int onePerSeconds) + FixClock(); + + // Arrange + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + Context contextPassedToRetryAfter = null; + Func retryAfterFactory = (t, ctx) => { - FixClock(); - - // Arrange - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - Context contextPassedToRetryAfter = null; - Func retryAfterFactory = (t, ctx) => - { - contextPassedToRetryAfter = ctx; - return new ResultClassWithRetryAfter(t); - }; - var rateLimiter = GetPolicyViaSyntax(1, onePer, 1, retryAfterFactory); - - // Arrange - drain first permitted execution after initialising. - ShouldPermitAnExecution(rateLimiter); - - // Arrange - // (do nothing - time not advanced) - - // Act - try another execution. - Context contextToPassIn = new Context(); - var resultExpectedBlocked = TryExecuteThroughPolicy(rateLimiter, contextToPassIn, new ResultClassWithRetryAfter(ResultPrimitive.Good)); - - // Assert - should be blocked - time not advanced. - resultExpectedBlocked.ResultCode.Should().NotBe(ResultPrimitive.Good); - // Result should be expressed per the retryAfterFactory. - resultExpectedBlocked.RetryAfter.Should().Be(onePer); - // Context should have been passed to the retryAfterFactory. - contextPassedToRetryAfter.Should().NotBeNull(); - contextPassedToRetryAfter.Should().BeSameAs(contextToPassIn); - } + contextPassedToRetryAfter = ctx; + return new ResultClassWithRetryAfter(t); + }; + var rateLimiter = GetPolicyViaSyntax(1, onePer, 1, retryAfterFactory); + + // Arrange - drain first permitted execution after initialising. + ShouldPermitAnExecution(rateLimiter); + + // Arrange + // (do nothing - time not advanced) + + // Act - try another execution. + Context contextToPassIn = new Context(); + var resultExpectedBlocked = TryExecuteThroughPolicy(rateLimiter, contextToPassIn, new ResultClassWithRetryAfter(ResultPrimitive.Good)); + + // Assert - should be blocked - time not advanced. + resultExpectedBlocked.ResultCode.Should().NotBe(ResultPrimitive.Good); + // Result should be expressed per the retryAfterFactory. + resultExpectedBlocked.RetryAfter.Should().Be(onePer); + // Context should have been passed to the retryAfterFactory. + contextPassedToRetryAfter.Should().NotBeNull(); + contextPassedToRetryAfter.Should().BeSameAs(contextToPassIn); } } diff --git a/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs index b5bcd58360a..32753153f45 100644 --- a/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs @@ -5,50 +5,49 @@ using Polly.Utilities; using Xunit.Sdk; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +public abstract class RateLimitSpecsBase { - public abstract class RateLimitSpecsBase + /// + /// Asserts that the actionContainingAssertions will succeed without or , within the given timespan. Checks are made each time a status-change pulse is received from the s executing through the bulkhead. + /// + /// The allowable timespan. + /// The action containing fluent assertions, which must succeed within the timespan. + protected void Within(TimeSpan timeSpan, Action actionContainingAssertions) { - /// - /// Asserts that the actionContainingAssertions will succeed without or , within the given timespan. Checks are made each time a status-change pulse is received from the s executing through the bulkhead. - /// - /// The allowable timespan. - /// The action containing fluent assertions, which must succeed within the timespan. - protected void Within(TimeSpan timeSpan, Action actionContainingAssertions) - { - TimeSpan retryInterval = TimeSpan.FromSeconds(0.2); + TimeSpan retryInterval = TimeSpan.FromSeconds(0.2); - Stopwatch watch = Stopwatch.StartNew(); - while (true) + Stopwatch watch = Stopwatch.StartNew(); + while (true) + { + try { - try - { - actionContainingAssertions.Invoke(); - break; - } - catch (Exception e) - { - if (!(e is AssertionFailedException || e is XunitException)) { throw; } - - if (watch.Elapsed > timeSpan) { throw; } - - Thread.Sleep(retryInterval); - } + actionContainingAssertions.Invoke(); + break; } - } + catch (Exception e) + { + if (!(e is AssertionFailedException || e is XunitException)) { throw; } - protected static void FixClock() - { - DateTimeOffset now = DateTimeOffset.UtcNow; - SystemClock.DateTimeOffsetUtcNow = () => now; - } + if (watch.Elapsed > timeSpan) { throw; } - protected static void AdvanceClock(TimeSpan advance) - { - DateTimeOffset now = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => now + advance; + Thread.Sleep(retryInterval); + } } + } - protected static void AdvanceClock(long advanceTicks) => AdvanceClock(TimeSpan.FromTicks(advanceTicks)); + protected static void FixClock() + { + DateTimeOffset now = DateTimeOffset.UtcNow; + SystemClock.DateTimeOffsetUtcNow = () => now; } + + protected static void AdvanceClock(TimeSpan advance) + { + DateTimeOffset now = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => now + advance; + } + + protected static void AdvanceClock(long advanceTicks) => AdvanceClock(TimeSpan.FromTicks(advanceTicks)); } diff --git a/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs b/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs index c296f0d5bf7..1e30031e516 100644 --- a/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs +++ b/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs @@ -8,209 +8,208 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit +namespace Polly.Specs.RateLimit; + +[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] +public abstract class TokenBucketRateLimiterTestsBase : RateLimitSpecsBase, IDisposable { - [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] - public abstract class TokenBucketRateLimiterTestsBase : RateLimitSpecsBase, IDisposable - { - internal abstract IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity); + internal abstract IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity); - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) - { - FixClock(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) + { + FixClock(); - // Arrange - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, 1); + // Arrange + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, 1); - // Assert - first execution after initialising should always be permitted. - rateLimiter.ShouldPermitAnExecution(); + // Assert - first execution after initialising should always be permitted. + rateLimiter.ShouldPermitAnExecution(); - // Arrange - // (do nothing - time not advanced) + // Arrange + // (do nothing - time not advanced) - // Assert - should be blocked - time not advanced. - rateLimiter.ShouldNotPermitAnExecution(onePer); - } + // Assert - should be blocked - time not advanced. + rateLimiter.ShouldNotPermitAnExecution(onePer); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(50)] - public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(50)] + public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) + { + FixClock(); - // Arrange. - TimeSpan onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange. + TimeSpan onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Act - should be able to successfully take bucketCapacity items. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); + // Act - should be able to successfully take bucketCapacity items. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); - // Assert - should not be able to take any items (given time not advanced). - rateLimiter.ShouldNotPermitAnExecution(onePer); - } + // Assert - should not be able to take any items (given time not advanced). + rateLimiter.ShouldNotPermitAnExecution(onePer); + } - [Theory] - [InlineData(1, 1)] - [InlineData(2, 1)] - [InlineData(5, 1)] - [InlineData(1, 10)] - [InlineData(2, 10)] - [InlineData(5, 10)] - public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(1, 1)] + [InlineData(2, 1)] + [InlineData(5, 1)] + [InlineData(1, 10)] + [InlineData(2, 10)] + [InlineData(5, 10)] + public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) + { + FixClock(); - // Arrange - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval - int experimentRepeats = bucketCapacity * 3; - TimeSpan shortfallFromInterval = TimeSpan.FromTicks(1); - TimeSpan notQuiteInterval = onePer - shortfallFromInterval; - for (int i = 0; i < experimentRepeats; i++) - { - // Arrange - Advance clock not quite to the interval - AdvanceClock(notQuiteInterval.Ticks); + // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval + int experimentRepeats = bucketCapacity * 3; + TimeSpan shortfallFromInterval = TimeSpan.FromTicks(1); + TimeSpan notQuiteInterval = onePer - shortfallFromInterval; + for (int i = 0; i < experimentRepeats; i++) + { + // Arrange - Advance clock not quite to the interval + AdvanceClock(notQuiteInterval.Ticks); - // Assert - should not quite be able to issue another token - rateLimiter.ShouldNotPermitAnExecution(shortfallFromInterval); + // Assert - should not quite be able to issue another token + rateLimiter.ShouldNotPermitAnExecution(shortfallFromInterval); - // Arrange - Advance clock to the interval - AdvanceClock(shortfallFromInterval.Ticks); + // Arrange - Advance clock to the interval + AdvanceClock(shortfallFromInterval.Ticks); - // Act - rateLimiter.ShouldPermitAnExecution(); + // Act + rateLimiter.ShouldPermitAnExecution(); - // Assert - but cannot get another token straight away - rateLimiter.ShouldNotPermitAnExecution(); - } + // Assert - but cannot get another token straight away + rateLimiter.ShouldNotPermitAnExecution(); } + } - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) + { + FixClock(); - // Arrange - int onePerSeconds = 1; - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Arrange - advance exactly enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity); + // Arrange - advance exactly enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity); - // Assert - expect full bucket capacity but no more - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); - } + // Assert - expect full bucket capacity but no more + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); + } - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) - { - (bucketCapacity % 2).Should().Be(0); + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) + { + (bucketCapacity % 2).Should().Be(0); - FixClock(); + FixClock(); - // Arrange - int onePerSeconds = 1; - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); - // Assert - expect full bucket capacity but no more - rateLimiter.ShouldPermitNExecutions(bucketCapacity / 2); - rateLimiter.ShouldNotPermitAnExecution(); - } + // Assert - expect full bucket capacity but no more + rateLimiter.ShouldPermitNExecutions(bucketCapacity / 2); + rateLimiter.ShouldNotPermitAnExecution(); + } - [Theory] - [InlineData(100, 2)] - [InlineData(100, 5)] - public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) - { - multipleRefillTimePassed.Should().BeGreaterThan(1); + [Theory] + [InlineData(100, 2)] + [InlineData(100, 5)] + public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) + { + multipleRefillTimePassed.Should().BeGreaterThan(1); - FixClock(); + FixClock(); - // Arrange - int onePerSeconds = 1; - TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); - // Assert - expect full bucket capacity but no more - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); - } + // Assert - expect full bucket capacity but no more + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); + } - [Theory] - [InlineData(2)] - [InlineData(5)] - [InlineData(100)] - public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) - { - FixClock(); + [Theory] + [InlineData(2)] + [InlineData(5)] + [InlineData(100)] + public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) + { + FixClock(); - // Arrange - TimeSpan onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetRateLimiter(onePer, 1); + // Arrange + TimeSpan onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetRateLimiter(onePer, 1); - // Arrange - parallel tasks all waiting on a manual reset event. - ManualResetEventSlim gate = new ManualResetEventSlim(); - Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; - for (int i = 0; i < parallelContention; i++) + // Arrange - parallel tasks all waiting on a manual reset event. + ManualResetEventSlim gate = new ManualResetEventSlim(); + Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; + for (int i = 0; i < parallelContention; i++) + { + tasks[i] = Task.Run(() => { - tasks[i] = Task.Run(() => - { - gate.Wait(); - return rateLimiter.PermitExecution(); - }); - } - - // Act - release gate. - gate.Set(); - Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); - - // Assert - one should have permitted execution, n-1 not. - var results = tasks.Select(t => t.Result).ToList(); - results.Count(r => r.permitExecution).Should().Be(1); - results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); + gate.Wait(); + return rateLimiter.PermitExecution(); + }); } + + // Act - release gate. + gate.Set(); + Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); + + // Assert - one should have permitted execution, n-1 not. + var results = tasks.Select(t => t.Result).ToList(); + results.Count(r => r.permitExecution).Should().Be(1); + results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); } } diff --git a/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs index 04164ec0f7e..7738cc71d6f 100644 --- a/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs @@ -5,260 +5,259 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Registry +namespace Polly.Specs.Registry; + +public class ConcurrentPolicyRegistrySpecs { - public class ConcurrentPolicyRegistrySpecs + IConcurrentPolicyRegistry _registry; + + public ConcurrentPolicyRegistrySpecs() + { + _registry = new PolicyRegistry(); + } + + [Fact] + public void Should_be_able_to_add_Policy_using_TryAdd() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + var insert = _registry.TryAdd(key, policy); + _registry.Count.Should().Be(1); + insert.Should().Be(true); + + Policy policy2 = Policy.NoOp(); + string key2 = Guid.NewGuid().ToString(); + + var insert2 = _registry.TryAdd(key2, policy2); + _registry.Count.Should().Be(2); + insert2.Should().Be(true); + } + + [Fact] + public void Should_be_able_to_add_PolicyTResult_using_TryAdd() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + var insert = _registry.TryAdd(key, policy); + _registry.Count.Should().Be(1); + insert.Should().Be(true); + + Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key2 = Guid.NewGuid().ToString(); + + var insert2 = _registry.TryAdd(key2, policy2); + _registry.Count.Should().Be(2); + insert2.Should().Be(true); + } + + [Fact] + public void Should_be_able_to_add_Policy_by_interface_using_TryAdd() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + var insert = _registry.TryAdd(key, policy); + _registry.Count.Should().Be(1); + insert.Should().Be(true); + + ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key2 = Guid.NewGuid().ToString(); + + var insert2 = _registry.TryAdd(key2, policy2); + _registry.Count.Should().Be(2); + insert2.Should().Be(true); + } + + [Fact] + public void Should_be_able_to_remove_policy_with_TryRemove() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + bool removed = _registry.TryRemove(key, out IsPolicy removedPolicy); + _registry.Count.Should().Be(0); + removedPolicy.Should().BeSameAs(policy); + removed.Should().BeTrue(); + } + + [Fact] + public void Should_report_false_from_TryRemove_if_no_Policy() { - IConcurrentPolicyRegistry _registry; - - public ConcurrentPolicyRegistrySpecs() - { - _registry = new PolicyRegistry(); - } - - [Fact] - public void Should_be_able_to_add_Policy_using_TryAdd() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - var insert = _registry.TryAdd(key, policy); - _registry.Count.Should().Be(1); - insert.Should().Be(true); - - Policy policy2 = Policy.NoOp(); - string key2 = Guid.NewGuid().ToString(); - - var insert2 = _registry.TryAdd(key2, policy2); - _registry.Count.Should().Be(2); - insert2.Should().Be(true); - } - - [Fact] - public void Should_be_able_to_add_PolicyTResult_using_TryAdd() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - var insert = _registry.TryAdd(key, policy); - _registry.Count.Should().Be(1); - insert.Should().Be(true); - - Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key2 = Guid.NewGuid().ToString(); - - var insert2 = _registry.TryAdd(key2, policy2); - _registry.Count.Should().Be(2); - insert2.Should().Be(true); - } - - [Fact] - public void Should_be_able_to_add_Policy_by_interface_using_TryAdd() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - var insert = _registry.TryAdd(key, policy); - _registry.Count.Should().Be(1); - insert.Should().Be(true); - - ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key2 = Guid.NewGuid().ToString(); - - var insert2 = _registry.TryAdd(key2, policy2); - _registry.Count.Should().Be(2); - insert2.Should().Be(true); - } - - [Fact] - public void Should_be_able_to_remove_policy_with_TryRemove() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - bool removed = _registry.TryRemove(key, out IsPolicy removedPolicy); - _registry.Count.Should().Be(0); - removedPolicy.Should().BeSameAs(policy); - removed.Should().BeTrue(); - } - - [Fact] - public void Should_report_false_from_TryRemove_if_no_Policy() - { - string key = Guid.NewGuid().ToString(); + bool removed = _registry.TryRemove(key, out IsPolicy removedPolicy); + removed.Should().BeFalse(); + } - bool removed = _registry.TryRemove(key, out IsPolicy removedPolicy); - removed.Should().BeFalse(); - } - - [Fact] - public void Should_be_able_to_update_policy_with_TryUpdate() - { - Policy existingPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); - - Policy newPolicy = Policy.NoOp(); + [Fact] + public void Should_be_able_to_update_policy_with_TryUpdate() + { + Policy existingPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - bool updated = _registry.TryUpdate(key, newPolicy, existingPolicy); - - updated.Should().BeTrue(); - _registry[key].Should().BeSameAs(newPolicy); - } - - [Fact] - public void Should_not_update_policy_with_TryUpdate_when_existingPolicy_mismatch() - { - Policy existingPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + Policy newPolicy = Policy.NoOp(); - NoOpPolicy someOtherPolicy = Policy.NoOp(); - Policy newPolicy = Policy.NoOp(); + bool updated = _registry.TryUpdate(key, newPolicy, existingPolicy); - bool updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); + updated.Should().BeTrue(); + _registry[key].Should().BeSameAs(newPolicy); + } - updated.Should().BeFalse(); - _registry[key].Should().BeSameAs(existingPolicy); - } + [Fact] + public void Should_not_update_policy_with_TryUpdate_when_existingPolicy_mismatch() + { + Policy existingPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - [Fact] - public void Should_not_update_policy_with_TryUpdate_when_no_existing_value() - { - string key = Guid.NewGuid().ToString(); + NoOpPolicy someOtherPolicy = Policy.NoOp(); + Policy newPolicy = Policy.NoOp(); - NoOpPolicy someOtherPolicy = Policy.NoOp(); - Policy newPolicy = Policy.NoOp(); + bool updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); - bool updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); + updated.Should().BeFalse(); + _registry[key].Should().BeSameAs(existingPolicy); + } - updated.Should().BeFalse(); - _registry.ContainsKey(key).Should().BeFalse(); - } + [Fact] + public void Should_not_update_policy_with_TryUpdate_when_no_existing_value() + { + string key = Guid.NewGuid().ToString(); - [Fact] - public void Should_add_with_GetOrAdd_with_value_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - var returnedPolicy = _registry.GetOrAdd(key, newPolicy); + NoOpPolicy someOtherPolicy = Policy.NoOp(); + Policy newPolicy = Policy.NoOp(); - returnedPolicy.Should().BeSameAs(newPolicy); - } + bool updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); - [Fact] - public void Should_add_with_GetOrAdd_with_factory_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + updated.Should().BeFalse(); + _registry.ContainsKey(key).Should().BeFalse(); + } - var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); + [Fact] + public void Should_add_with_GetOrAdd_with_value_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); - returnedPolicy.Should().BeSameAs(newPolicy); - } + var returnedPolicy = _registry.GetOrAdd(key, newPolicy); - [Fact] - public void Should_return_existing_with_GetOrAdd_with_value_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + returnedPolicy.Should().BeSameAs(newPolicy); + } - Policy newPolicy = Policy.NoOp(); + [Fact] + public void Should_add_with_GetOrAdd_with_factory_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); - var returnedPolicy = _registry.GetOrAdd(key, newPolicy); + var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); - returnedPolicy.Should().BeSameAs(existingPolicy); - } + returnedPolicy.Should().BeSameAs(newPolicy); + } - [Fact] - public void Should_return_existing_with_GetOrAdd_with_factory_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + [Fact] + public void Should_return_existing_with_GetOrAdd_with_value_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - Policy newPolicy = Policy.NoOp(); + Policy newPolicy = Policy.NoOp(); - var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); + var returnedPolicy = _registry.GetOrAdd(key, newPolicy); - returnedPolicy.Should().BeSameAs(existingPolicy); - } + returnedPolicy.Should().BeSameAs(existingPolicy); + } - [Fact] - public void Should_add_with_AddOrUpdate_with_value_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_return_existing_with_GetOrAdd_with_factory_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - var returnedPolicy = _registry.AddOrUpdate( - key, - newPolicy, - (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); + Policy newPolicy = Policy.NoOp(); - returnedPolicy.Should().BeSameAs(newPolicy); - } + var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); - [Fact] - public void Should_add_with_AddOrUpdate_with_addfactory_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + returnedPolicy.Should().BeSameAs(existingPolicy); + } + [Fact] + public void Should_add_with_AddOrUpdate_with_value_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); - var returnedPolicy = _registry.AddOrUpdate( - key, - _ => newPolicy, - (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); + var returnedPolicy = _registry.AddOrUpdate( + key, + newPolicy, + (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); - returnedPolicy.Should().BeSameAs(newPolicy); - } + returnedPolicy.Should().BeSameAs(newPolicy); + } - [Fact] - public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addvalue_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + [Fact] + public void Should_add_with_AddOrUpdate_with_addfactory_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); - const string policyKeyToDecorate = "SomePolicyKey"; - Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); + var returnedPolicy = _registry.AddOrUpdate( + key, + _ => newPolicy, + (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); - var returnedPolicy = _registry.AddOrUpdate( - key, - otherPolicyNotExpectingToAdd, - (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); + returnedPolicy.Should().BeSameAs(newPolicy); + } - returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); - returnedPolicy.Should().BeSameAs(existingPolicy); - returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); - } + [Fact] + public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addvalue_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - [Fact] - public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addfactory_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + const string policyKeyToDecorate = "SomePolicyKey"; - const string policyKeyToDecorate = "SomePolicyKey"; + Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); - Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); + var returnedPolicy = _registry.AddOrUpdate( + key, + otherPolicyNotExpectingToAdd, + (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); - var returnedPolicy = _registry.AddOrUpdate( - key, - _ => otherPolicyNotExpectingToAdd, - (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); + returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); + returnedPolicy.Should().BeSameAs(existingPolicy); + returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); + } + + [Fact] + public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addfactory_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); - returnedPolicy.Should().BeSameAs(existingPolicy); - returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); - } + const string policyKeyToDecorate = "SomePolicyKey"; + Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); + + var returnedPolicy = _registry.AddOrUpdate( + key, + _ => otherPolicyNotExpectingToAdd, + (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); + + returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); + returnedPolicy.Should().BeSameAs(existingPolicy); + returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); } + } diff --git a/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs index 8772f367f03..bfa28e6f9c7 100644 --- a/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs @@ -8,466 +8,465 @@ using Polly.Specs.Helpers; using Moq; -namespace Polly.Specs.Registry +namespace Polly.Specs.Registry; + +public class PolicyRegistrySpecs { - public class PolicyRegistrySpecs - { - IPolicyRegistry _registry; - - public PolicyRegistrySpecs() - { - _registry = new PolicyRegistry(); - } - - #region Tests for adding Policy - - [Fact] - public void Should_be_able_to_add_Policy_using_Add() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - Policy policy2 = Policy.NoOp(); - string key2 = Guid.NewGuid().ToString(); + IPolicyRegistry _registry; + + public PolicyRegistrySpecs() + { + _registry = new PolicyRegistry(); + } + + #region Tests for adding Policy + + [Fact] + public void Should_be_able_to_add_Policy_using_Add() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + Policy policy2 = Policy.NoOp(); + string key2 = Guid.NewGuid().ToString(); + + _registry.Add(key2, policy2); + _registry.Count.Should().Be(2); + } + + [Fact] + public void Should_be_able_to_add_PolicyTResult_using_Add() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key2 = Guid.NewGuid().ToString(); + + _registry.Add(key2, policy2); + _registry.Count.Should().Be(2); + } + + [Fact] + public void Should_be_able_to_add_Policy_by_interface_using_Add() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key2 = Guid.NewGuid().ToString(); + + _registry.Add>(key2, policy2); + _registry.Count.Should().Be(2); + } + + [Fact] + public void Should_be_able_to_add_Policy_using_Indexer() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry[key] = policy; + _registry.Count.Should().Be(1); + + Policy policy2 = Policy.NoOp(); + string key2 = Guid.NewGuid().ToString(); + + _registry[key2] = policy2; + _registry.Count.Should().Be(2); + } + + [Fact] + public void Should_be_able_to_add_PolicyTResult_using_Indexer() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry[key] = policy; + _registry.Count.Should().Be(1); + + Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key2 = Guid.NewGuid().ToString(); + + _registry[key2] = policy2; + _registry.Count.Should().Be(2); + } + + [Fact] + public void Should_not_be_able_to_add_Policy_with_duplicate_key_using_Add() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Invoking(r => r.Add(key, policy)) + .Should().NotThrow(); + + _registry.Invoking(r => r.Add(key, policy)) + .Should().Throw(); + + _registry.Count.Should().Be(1); + } + + [Fact] + public void Should_be_able_to_overwrite_existing_Policy_if_key_exists_when_inserting_using_Indexer() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + _registry.Add(key, policy); + + Policy policy_new = Policy.NoOp(); + _registry[key] = policy_new; - _registry.Add(key2, policy2); - _registry.Count.Should().Be(2); - } - - [Fact] - public void Should_be_able_to_add_PolicyTResult_using_Add() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + _registry.Count.Should().Be(1); + + _registry.Get(key).Should().BeSameAs(policy_new); + } + + [Fact] + public void Should_be_able_to_overwrite_existing_PolicyTResult_if_key_exists_when_inserting_using_Indexer() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + _registry.Add>(key, policy); + + Policy policy_new = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + _registry[key] = policy_new; + + _registry.Count.Should().Be(1); + + _registry.Get>(key).Should().BeSameAs(policy_new); + } + + [Fact] + public void Should_throw_when_adding_Policy_using_Add_when_key_is_null() + { + string key = null; + Policy policy = Policy.NoOp(); + _registry.Invoking(r => r.Add(key, policy)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_adding_Policy_using_Indexer_when_key_is_null() + { + string key = null; + Policy policy = Policy.NoOp(); + _registry.Invoking(r => r[key] = policy) + .Should().Throw(); + } + + #endregion + + #region Tests for retrieving policy + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + + _registry.Add(key, policy); + _registry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + + _registry.Add(key, policy); + _registry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; + + _registry.Add(key, policy); + _registry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Get() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Get(key).Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Get>(key).Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Get>(key).Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry[key].Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry[key].Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry[key].Should().BeSameAs(policy); + } + + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() + { + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + bool result = false; + + _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); + + result.Should().BeFalse(); + } + + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() + { + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + bool result = false; + + _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); + + result.Should().BeFalse(); + } + + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() + { + string key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; + bool result = false; + + _registry.Invoking(r => result = r.TryGet>(key, out outPolicy)) + .Should().NotThrow(); + + result.Should().BeFalse(); + } + + [Fact] + public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() + { + string key = Guid.NewGuid().ToString(); + Policy policy = null; + _registry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() + { + string key = Guid.NewGuid().ToString(); + Policy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() + { + string key = Guid.NewGuid().ToString(); + ISyncPolicy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() + { + string key = Guid.NewGuid().ToString(); + IsPolicy outPolicy = null; + _registry.Invoking(r => outPolicy = r[key]) + .Should().Throw(); + } + + + [Fact] + public void Should_throw_when_retrieving_using_Get_when_key_is_null() + { + string key = null; + Policy policy = null; + _registry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() + { + string key = null; + Policy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() + { + string key = null; + ISyncPolicy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() + { + string key = null; + IsPolicy policy = null; + _registry.Invoking(r => policy = r[key]) + .Should().Throw(); + } + #endregion + + #region Tests for removing policy + [Fact] + public void Should_be_able_to_clear_registry() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + Policy policy2 = Policy.NoOp(); + string key2 = Guid.NewGuid().ToString(); + + _registry.Add(key2, policy2); + _registry.Count.Should().Be(2); + + _registry.Clear(); + _registry.Count.Should().Be(0); + } + + [Fact] + public void Should_be_able_to_remove_policy() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + _registry.Remove(key); + _registry.Count.Should().Be(0); + } + + [Fact] + public void Should_throw_when_removing_Policy_when_key_is_null() + { + string key = null; + _registry.Invoking(r => r.Remove(key)) + .Should().Throw(); + } + #endregion + + #region Tests for checking if key exists + + [Fact] + public void Should_be_able_to_check_if_key_exists() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.ContainsKey(key).Should().BeTrue(); + + string key2 = Guid.NewGuid().ToString(); + _registry.ContainsKey(key2).Should().BeFalse(); + } + + [Fact] + public void Should_throw_when_checking_if_key_exists_when_key_is_null() + { + string key = null; + _registry.Invoking(r => r.ContainsKey(key)) + .Should().Throw(); + } + #endregion + + #region Tests for the constructor + [Fact] + public void Constructor_Called_With_A_Registry_Parameter_Should_Assign_The_Passed_In_Registry_To_The_Registry_Field() + { + var testDictionary = new Mock>(); + var testRegistry = new PolicyRegistry(testDictionary.Object); + + //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. + var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); + var registryFieldValue = registryField.GetValue(testRegistry); + registryFieldValue.Should().Be(testDictionary.Object); + } + + [Fact] + public void Constructor_Called_With_Default_Parameters_Assigns_A_ConcurrentDictionary_Of_TKey_And_IsPolicy_To_The_Private_Registry_Field() + { + var expectedDictionaryType = typeof(ConcurrentDictionary); + var testRegistry = new PolicyRegistry(); + + //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. + var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); + var registryFieldValue = registryField.GetValue(testRegistry); + registryFieldValue.Should().BeOfType(expectedDictionaryType); + } - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key2 = Guid.NewGuid().ToString(); - - _registry.Add(key2, policy2); - _registry.Count.Should().Be(2); - } - - [Fact] - public void Should_be_able_to_add_Policy_by_interface_using_Add() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key2 = Guid.NewGuid().ToString(); - - _registry.Add>(key2, policy2); - _registry.Count.Should().Be(2); - } - - [Fact] - public void Should_be_able_to_add_Policy_using_Indexer() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry[key] = policy; - _registry.Count.Should().Be(1); - - Policy policy2 = Policy.NoOp(); - string key2 = Guid.NewGuid().ToString(); - - _registry[key2] = policy2; - _registry.Count.Should().Be(2); - } - - [Fact] - public void Should_be_able_to_add_PolicyTResult_using_Indexer() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry[key] = policy; - _registry.Count.Should().Be(1); - - Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key2 = Guid.NewGuid().ToString(); - - _registry[key2] = policy2; - _registry.Count.Should().Be(2); - } - - [Fact] - public void Should_not_be_able_to_add_Policy_with_duplicate_key_using_Add() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Invoking(r => r.Add(key, policy)) - .Should().NotThrow(); - - _registry.Invoking(r => r.Add(key, policy)) - .Should().Throw(); - - _registry.Count.Should().Be(1); - } - - [Fact] - public void Should_be_able_to_overwrite_existing_Policy_if_key_exists_when_inserting_using_Indexer() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - - Policy policy_new = Policy.NoOp(); - _registry[key] = policy_new; - - _registry.Count.Should().Be(1); - - _registry.Get(key).Should().BeSameAs(policy_new); - } - - [Fact] - public void Should_be_able_to_overwrite_existing_PolicyTResult_if_key_exists_when_inserting_using_Indexer() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - _registry.Add>(key, policy); - - Policy policy_new = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - _registry[key] = policy_new; - - _registry.Count.Should().Be(1); - - _registry.Get>(key).Should().BeSameAs(policy_new); - } - - [Fact] - public void Should_throw_when_adding_Policy_using_Add_when_key_is_null() - { - string key = null; - Policy policy = Policy.NoOp(); - _registry.Invoking(r => r.Add(key, policy)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_adding_Policy_using_Indexer_when_key_is_null() - { - string key = null; - Policy policy = Policy.NoOp(); - _registry.Invoking(r => r[key] = policy) - .Should().Throw(); - } - - #endregion - - #region Tests for retrieving policy - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - - _registry.Add(key, policy); - _registry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - - _registry.Add(key, policy); - _registry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; - - _registry.Add(key, policy); - _registry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Get() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Get(key).Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Get>(key).Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Get>(key).Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry[key].Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry[key].Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry[key].Should().BeSameAs(policy); - } - - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() - { - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - bool result = false; - - _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); - - result.Should().BeFalse(); - } - - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() - { - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - bool result = false; - - _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); - - result.Should().BeFalse(); - } - - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() - { - string key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; - bool result = false; - - _registry.Invoking(r => result = r.TryGet>(key, out outPolicy)) - .Should().NotThrow(); - - result.Should().BeFalse(); - } - - [Fact] - public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() - { - string key = Guid.NewGuid().ToString(); - Policy policy = null; - _registry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() - { - string key = Guid.NewGuid().ToString(); - Policy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() - { - string key = Guid.NewGuid().ToString(); - ISyncPolicy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() - { - string key = Guid.NewGuid().ToString(); - IsPolicy outPolicy = null; - _registry.Invoking(r => outPolicy = r[key]) - .Should().Throw(); - } - - - [Fact] - public void Should_throw_when_retrieving_using_Get_when_key_is_null() - { - string key = null; - Policy policy = null; - _registry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() - { - string key = null; - Policy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() - { - string key = null; - ISyncPolicy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() - { - string key = null; - IsPolicy policy = null; - _registry.Invoking(r => policy = r[key]) - .Should().Throw(); - } - #endregion - - #region Tests for removing policy - [Fact] - public void Should_be_able_to_clear_registry() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - Policy policy2 = Policy.NoOp(); - string key2 = Guid.NewGuid().ToString(); - - _registry.Add(key2, policy2); - _registry.Count.Should().Be(2); - - _registry.Clear(); - _registry.Count.Should().Be(0); - } - - [Fact] - public void Should_be_able_to_remove_policy() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - _registry.Remove(key); - _registry.Count.Should().Be(0); - } - - [Fact] - public void Should_throw_when_removing_Policy_when_key_is_null() - { - string key = null; - _registry.Invoking(r => r.Remove(key)) - .Should().Throw(); - } - #endregion - - #region Tests for checking if key exists - - [Fact] - public void Should_be_able_to_check_if_key_exists() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.ContainsKey(key).Should().BeTrue(); - - string key2 = Guid.NewGuid().ToString(); - _registry.ContainsKey(key2).Should().BeFalse(); - } - - [Fact] - public void Should_throw_when_checking_if_key_exists_when_key_is_null() - { - string key = null; - _registry.Invoking(r => r.ContainsKey(key)) - .Should().Throw(); - } - #endregion - - #region Tests for the constructor - [Fact] - public void Constructor_Called_With_A_Registry_Parameter_Should_Assign_The_Passed_In_Registry_To_The_Registry_Field() - { - var testDictionary = new Mock>(); - var testRegistry = new PolicyRegistry(testDictionary.Object); - - //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. - var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); - var registryFieldValue = registryField.GetValue(testRegistry); - registryFieldValue.Should().Be(testDictionary.Object); - } - - [Fact] - public void Constructor_Called_With_Default_Parameters_Assigns_A_ConcurrentDictionary_Of_TKey_And_IsPolicy_To_The_Private_Registry_Field() - { - var expectedDictionaryType = typeof(ConcurrentDictionary); - var testRegistry = new PolicyRegistry(); - - //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. - var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); - var registryFieldValue = registryField.GetValue(testRegistry); - registryFieldValue.Should().BeOfType(expectedDictionaryType); - } - - #endregion - } -} \ No newline at end of file + #endregion +} diff --git a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs index 57dab67b038..eb6a233ae7a 100644 --- a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs @@ -6,287 +6,286 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Registry +namespace Polly.Specs.Registry; + +public class ReadOnlyPolicyRegistrySpecs { - public class ReadOnlyPolicyRegistrySpecs + IPolicyRegistry _registry; + + IReadOnlyPolicyRegistry ReadOnlyRegistry { get{ return _registry; } } + + public ReadOnlyPolicyRegistrySpecs() { - IPolicyRegistry _registry; + _registry = new PolicyRegistry(); + } - IReadOnlyPolicyRegistry ReadOnlyRegistry { get{ return _registry; } } + #region Tests for retrieving policy - public ReadOnlyPolicyRegistrySpecs() - { - _registry = new PolicyRegistry(); - } + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; - #region Tests for retrieving policy + _registry.Add(key, policy); + ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; - _registry.Add(key, policy); - ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; - _registry.Add(key, policy); - ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Get() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.Get(key).Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Get() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry.Get(key).Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry[key].Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry[key].Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry[key].Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + string key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry[key].Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry[key].Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() + { + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + bool result = false; - _registry.Add(key, policy); - ReadOnlyRegistry[key].Should().BeSameAs(policy); - } + ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() - { - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - bool result = false; + result.Should().BeFalse(); + } - ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() + { + string key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + bool result = false; - result.Should().BeFalse(); - } + ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() - { - string key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - bool result = false; + result.Should().BeFalse(); + } - ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() + { + string key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; + bool result = false; - result.Should().BeFalse(); - } + ReadOnlyRegistry.Invoking(r => result = r.TryGet>(key, out outPolicy)) + .Should().NotThrow(); - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() - { - string key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; - bool result = false; + result.Should().BeFalse(); + } - ReadOnlyRegistry.Invoking(r => result = r.TryGet>(key, out outPolicy)) - .Should().NotThrow(); + [Fact] + public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() + { + string key = Guid.NewGuid().ToString(); + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } - result.Should().BeFalse(); - } + [Fact] + public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() + { + string key = Guid.NewGuid().ToString(); + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } - [Fact] - public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() - { - string key = Guid.NewGuid().ToString(); - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() - { - string key = Guid.NewGuid().ToString(); - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() - { - string key = Guid.NewGuid().ToString(); - ISyncPolicy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() - { - string key = Guid.NewGuid().ToString(); - IsPolicy outPolicy = null; - ReadOnlyRegistry.Invoking(r => outPolicy = r[key]) - .Should().Throw(); - } + [Fact] + public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() + { + string key = Guid.NewGuid().ToString(); + ISyncPolicy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + [Fact] + public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() + { + string key = Guid.NewGuid().ToString(); + IsPolicy outPolicy = null; + ReadOnlyRegistry.Invoking(r => outPolicy = r[key]) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_retrieving_using_Get_when_key_is_null() - { - string key = null; - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() - { - string key = null; - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() - { - string key = null; - ISyncPolicy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() - { - string key = null; - IsPolicy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r[key]) - .Should().Throw(); - } - #endregion - #region Tests for checking if key exists + [Fact] + public void Should_throw_when_retrieving_using_Get_when_key_is_null() + { + string key = null; + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } - [Fact] - public void Should_be_able_to_check_if_key_exists() - { - Policy policy = Policy.NoOp(); - string key = Guid.NewGuid().ToString(); + [Fact] + public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() + { + string key = null; + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } - _registry.Add(key, policy); - ReadOnlyRegistry.ContainsKey(key).Should().BeTrue(); + [Fact] + public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() + { + string key = null; + ISyncPolicy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } - string key2 = Guid.NewGuid().ToString(); - ReadOnlyRegistry.ContainsKey(key2).Should().BeFalse(); - } + [Fact] + public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() + { + string key = null; + IsPolicy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r[key]) + .Should().Throw(); + } + #endregion - [Fact] - public void Should_throw_when_checking_if_key_exists_when_key_is_null() - { - string key = null; - ReadOnlyRegistry.Invoking(r => r.ContainsKey(key)) - .Should().Throw(); - } - #endregion - - #region Tests for the GetEnumerator method - - [Fact] - public void Calling_The_GetEnumerator_Method_Returning_A_IEnumerator_Of_KeyValuePair_Of_String_And_IsPolicy_Calls_The_Registrys_GetEnumerator_Method() - { - var testDictionary = new Mock>(); - var testRegistry = new PolicyRegistry(testDictionary.Object); + #region Tests for checking if key exists + + [Fact] + public void Should_be_able_to_check_if_key_exists() + { + Policy policy = Policy.NoOp(); + string key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + ReadOnlyRegistry.ContainsKey(key).Should().BeTrue(); + + string key2 = Guid.NewGuid().ToString(); + ReadOnlyRegistry.ContainsKey(key2).Should().BeFalse(); + } - testRegistry.GetEnumerator(); + [Fact] + public void Should_throw_when_checking_if_key_exists_when_key_is_null() + { + string key = null; + ReadOnlyRegistry.Invoking(r => r.ContainsKey(key)) + .Should().Throw(); + } + #endregion + + #region Tests for the GetEnumerator method + + [Fact] + public void Calling_The_GetEnumerator_Method_Returning_A_IEnumerator_Of_KeyValuePair_Of_String_And_IsPolicy_Calls_The_Registrys_GetEnumerator_Method() + { + var testDictionary = new Mock>(); + var testRegistry = new PolicyRegistry(testDictionary.Object); + + testRegistry.GetEnumerator(); - testDictionary.Verify(x => x.GetEnumerator(), Times.Once); - } + testDictionary.Verify(x => x.GetEnumerator(), Times.Once); + } + + #endregion - #endregion + #region Collection initializer tests - #region Collection initializer tests + [Fact] + public void Policies_Should_Be_Added_To_The_Registry_When_Using_Collection_Initializer_Syntax() + { + string key = Guid.NewGuid().ToString(); + var policy = Policy.NoOp(); + + var testRegistry = new PolicyRegistry + { + {key, policy} + }; - [Fact] - public void Policies_Should_Be_Added_To_The_Registry_When_Using_Collection_Initializer_Syntax() + testRegistry.Should().Equal(new Dictionary { - string key = Guid.NewGuid().ToString(); - var policy = Policy.NoOp(); - - var testRegistry = new PolicyRegistry - { - {key, policy} - }; - - testRegistry.Should().Equal(new Dictionary - { - {key, policy} - }); - } - #endregion + {key, policy} + }); } + #endregion } diff --git a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs index 00704b7ad50..0176d48661d 100644 --- a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs @@ -11,747 +11,746 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +public class RetryAsyncSpecs { - public class RetryAsyncSpecs + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action onRetry = (_, _) => { }; - - Action policy = () => Policy - .Handle() - .RetryAsync(-1, onRetry); + Action onRetry = (_, _) => { }; - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + Action policy = () => Policy + .Handle() + .RetryAsync(-1, onRetry); - [Fact] - public async Task Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .RetryAsync(3); + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .RetryAsync(3); - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(3); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(3); - [Fact] - public async Task Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .RetryAsync(3); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .RetryAsync(3); - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(3); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(3); - [Fact] - public async Task Should_throw_when_specified_exception_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .RetryAsync(3); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_specified_exception_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .RetryAsync(3); - [Fact] - public async Task Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(3); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(3); - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .RetryAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .RetryAsync(); - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(); - [Fact] - public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .RetryAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .RetryAsync(); - [Fact] - public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .RetryAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .RetryAsync(); - [Fact] - public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .RetryAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .RetryAsync(); - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryAsync(); - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - var policy = Policy - .Handle() - .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - await policy.RaiseExceptionAsync(3); + var policy = Policy + .Handle() + .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + await policy.RaiseExceptionAsync(3); - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - var policy = Policy - .Handle() - .RetryAsync(3, (exception, _) => retryExceptions.Add(exception)); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + var policy = Policy + .Handle() + .RetryAsync(3, (exception, _) => retryExceptions.Add(exception)); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - [Fact] - public async Task Should_call_onretry_with_a_handled_innerexception() - { - Exception passedToOnRetry = null; + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - var policy = Policy - .HandleInner() - .RetryAsync(3, (exception, _) => passedToOnRetry = exception); + [Fact] + public async Task Should_call_onretry_with_a_handled_innerexception() + { + Exception passedToOnRetry = null; - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + var policy = Policy + .HandleInner() + .RetryAsync(3, (exception, _) => passedToOnRetry = exception); - await policy.RaiseExceptionAsync(withInner); + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + await policy.RaiseExceptionAsync(withInner); - [Fact] - public async Task Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - var policy = Policy - .Handle() - .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); + [Fact] + public async Task Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + var policy = Policy + .Handle() + .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); - retryCounts.Should() - .BeEmpty(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - [Fact] - public async Task Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .RetryAsync(); + retryCounts.Should() + .BeEmpty(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); + [Fact] + public async Task Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .RetryAsync(); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextData = context); + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - policy.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextData = context); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + policy.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); - [Fact] - public async Task Should_call_onretry_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextData = context); + [Fact] + public async Task Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => { throw new DivideByZeroException(); }, - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrowAsync(); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextData = context); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => { throw new DivideByZeroException(); }, + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrowAsync(); - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_data() - { - Context capturedContext = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => capturedContext = context); + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_data() + { + Context capturedContext = null; - await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => capturedContext = context); - capturedContext.Should() - .BeEmpty(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + capturedContext.Should() + .BeEmpty(); + } - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - contextValue.Should().Be("original_value"); + policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + contextValue.Should().Be("original_value"); - contextValue.Should().Be("new_value"); - } + policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() + ); - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + contextValue.Should().Be("new_value"); + } - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), - new { key = "original_value" }.AsDictionary())) - .Should().NotThrowAsync(); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - contextValue.Should().Be("original_value"); + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), + new { key = "original_value" }.AsDictionary())) + .Should().NotThrowAsync(); - await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), - new { key = "new_value" }.AsDictionary())) - .Should().NotThrowAsync(); + contextValue.Should().Be("original_value"); - contextValue.Should().Be("new_value"); - } + await policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), + new { key = "new_value" }.AsDictionary())) + .Should().NotThrowAsync(); - [Fact] - public async Task Should_not_call_onretry_when_retry_count_is_zero() - { - bool retryInvoked = false; + contextValue.Should().Be("new_value"); + } - Action onRetry = (_, _) => { retryInvoked = true; }; + [Fact] + public async Task Should_not_call_onretry_when_retry_count_is_zero() + { + bool retryInvoked = false; - var policy = Policy - .Handle() - .RetryAsync(0, onRetry); + Action onRetry = (_, _) => { retryInvoked = true; }; - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + var policy = Policy + .Handle() + .RetryAsync(0, onRetry); - retryInvoked.Should().BeFalse(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - #region Async and cancellation tests + retryInvoked.Should().BeFalse(); + } - [Fact] - public async Task Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning to Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' of the retry policy would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + #region Async and cancellation tests - TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + [Fact] + public async Task Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning to Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' of the retry policy would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - int executeDelegateInvocations = 0; - int executeDelegateInvocationsWhenOnRetryExits = 0; + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var policy = Policy - .Handle() - .RetryAsync(async (_, _) => - { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; - await policy.Awaiting(p => p.ExecuteAsync(async () => + var policy = Policy + .Handle() + .RetryAsync(async (_, _) => { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - throw new DivideByZeroException(); - })).Should().ThrowAsync(); - - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + await policy.Awaiting(p => p.ExecuteAsync(async () => { - var policy = Policy - .Handle() - .RetryAsync(3); + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + throw new DivideByZeroException(); + })).Should().ThrowAsync(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .RetryAsync(3); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .RetryAsync(3); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(1 + 3); + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var policy = Policy + .Handle() + .RetryAsync(3); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(0); + } - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryAsync(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryAsync(3); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .RetryAsync(3); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryAsync(3); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .RetryAsync(3); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + { + var policy = Policy + .Handle() + .RetryAsync(3); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + { + var policy = Policy + .Handle() + .RetryAsync(3); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + Scenario scenario = new Scenario { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .RetryAsync(3, (_, _) => - { - cancellationTokenSource.Cancel(); - }); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .RetryAsync(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .RetryAsync(3); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - await policy.Awaiting(action) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public async Task Should_honour_and_report_cancellation_during_func_execution() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryAsync(3); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + await policy.Awaiting(action) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + result.Should().BeTrue(); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .RetryAsync(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - var ex = await policy.Awaiting(action).Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - result.Should().Be(null); + bool? result = null; - attemptsInvoked.Should().Be(1); - } + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - #endregion + result.Should().Be(null); + + attemptsInvoked.Should().Be(1); } -} \ No newline at end of file + + #endregion +} diff --git a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs index 81487a8c303..13f067b8483 100644 --- a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs @@ -11,520 +11,519 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +public class RetryForeverAsyncSpecs { - public class RetryForeverAsyncSpecs + [Fact] + public async Task Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() { - [Fact] - public async Task Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); - - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + var policy = Policy + .Handle() + .RetryForeverAsync(); - [Fact] - public async Task Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .RetryForeverAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .RetryForeverAsync(); - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .RetryForeverAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .RetryForeverAsync(); - [Fact] - public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .RetryForeverAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .RetryForeverAsync(); - [Fact] - public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .RetryForeverAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .RetryForeverAsync(); - [Fact] - public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .RetryForeverAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .RetryForeverAsync(); - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryForeverAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryForeverAsync(); - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] {"Exception #1", "Exception #2", "Exception #3"}; - var retryExceptions = new List(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - var policy = Policy - .Handle() - .RetryForeverAsync(exception => retryExceptions.Add(exception)); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] {"Exception #1", "Exception #2", "Exception #3"}; + var retryExceptions = new List(); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + var policy = Policy + .Handle() + .RetryForeverAsync(exception => retryExceptions.Add(exception)); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - [Fact] - public void Should_call_onretry_on_each_retry_with_the_passed_context() - { - IDictionary contextData = null; + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - var policy = Policy - .Handle() - .RetryForeverAsync((_, context) => contextData = context); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_passed_context() + { + IDictionary contextData = null; - policy.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); + var policy = Policy + .Handle() + .RetryForeverAsync((_, context) => contextData = context); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + policy.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var policy = Policy - .Handle() - .RetryForeverAsync((_, retryCount) => retryCounts.Add(retryCount)); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - policy.RaiseExceptionAsync(3); + var policy = Policy + .Handle() + .RetryForeverAsync((_, retryCount) => retryCounts.Add(retryCount)); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + policy.RaiseExceptionAsync(3); - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_data() - { - Context capturedContext = null; + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - var policy = Policy - .Handle() - .RetryForeverAsync((_, context) => capturedContext = context); + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_data() + { + Context capturedContext = null; - await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); + var policy = Policy + .Handle() + .RetryForeverAsync((_, context) => capturedContext = context); - capturedContext.Should() - .BeEmpty(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + capturedContext.Should() + .BeEmpty(); + } - var policy = Policy - .Handle() - .RetryForeverAsync((_, context) => contextValue = context["key"].ToString()); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); + var policy = Policy + .Handle() + .RetryForeverAsync((_, context) => contextValue = context["key"].ToString()); - contextValue.Should().Be("original_value"); + policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + contextValue.Should().Be("original_value"); - contextValue.Should().Be("new_value"); - } + policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() + ); - [Fact] - public async Task Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); + contextValue.Should().Be("new_value"); + } - var policy = Policy - .Handle() - .RetryForeverAsync(exception => retryExceptions.Add(exception)); + [Fact] + public async Task Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + var policy = Policy + .Handle() + .RetryForeverAsync(exception => retryExceptions.Add(exception)); - retryExceptions.Should() - .BeEmpty(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - [Fact] - public async Task Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + retryExceptions.Should() + .BeEmpty(); + } - TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + [Fact] + public async Task Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - int executeDelegateInvocations = 0; - int executeDelegateInvocationsWhenOnRetryExits = 0; + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var policy = Policy - .Handle() - .RetryForeverAsync(async _ => - { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; - await policy.Awaiting(p => p.ExecuteAsync(async () => + var policy = Policy + .Handle() + .RetryForeverAsync(async _ => { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } - })).Should().NotThrowAsync(); - - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } - - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + await policy.Awaiting(p => p.ExecuteAsync(async () => { - var policy = Policy - .Handle() - .RetryForeverAsync(); + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } + })).Should().NotThrowAsync(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(1); + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(0); + } + + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + Scenario scenario = new Scenario { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .RetryForeverAsync( - _ => - { - cancellationTokenSource.Cancel(); - }); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .RetryForeverAsync( + _ => + { + cancellationTokenSource.Cancel(); + }); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - await policy.Awaiting(action) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public async Task Should_honour_and_report_cancellation_during_func_execution() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .RetryForeverAsync(); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + await policy.Awaiting(action) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + result.Should().BeTrue(); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - var ex = await policy.Awaiting(action).Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().Be(null); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + + result.Should().Be(null); + + attemptsInvoked.Should().Be(1); } -} \ No newline at end of file + +} diff --git a/src/Polly.Specs/Retry/RetryForeverSpecs.cs b/src/Polly.Specs/Retry/RetryForeverSpecs.cs index bb6cdfe5c14..cdbb7aede9a 100644 --- a/src/Polly.Specs/Retry/RetryForeverSpecs.cs +++ b/src/Polly.Specs/Retry/RetryForeverSpecs.cs @@ -6,250 +6,249 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +public class RetryForeverSpecs { - public class RetryForeverSpecs - { - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action nullOnRetry = null; - - Action policy = () => Policy - .Handle() - .RetryForever(nullOnRetry); - - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } - - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action nullOnRetry = null; - - Action policy = () => Policy - .Handle() - .RetryForever(nullOnRetry); - - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } - - [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .RetryForever(); - - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } - - [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .RetryForever(); - - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } - - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); - - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } - - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } - - [Fact] - public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .RetryForeverAsync(); - - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } - - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } - - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryForeverAsync(); - - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } - - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] {"Exception #1", "Exception #2", "Exception #3"}; - var retryExceptions = new List(); - - var policy = Policy - .Handle() - .RetryForever(exception => retryExceptions.Add(exception)); - - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } - - [Fact] - public void Should_call_onretry_on_each_retry_with_the_passed_context() - { - IDictionary contextData = null; - - var policy = Policy - .Handle() - .RetryForever((_, context) => contextData = context); - - policy.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } - - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); - - var policy = Policy - .Handle() - .RetryForever((_, retryCount) => retryCounts.Add(retryCount)); - - policy.RaiseException(3); - - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } - - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); - - var policy = Policy - .Handle() - .RetryForever(exception => retryExceptions.Add(exception)); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - - retryExceptions.Should() - .BeEmpty(); - } - - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; - - var policy = Policy - .Handle() - .RetryForever((_, context) => contextValue = context["key"].ToString()); - - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); - - contextValue.Should().Be("original_value"); - - policy.RaiseException( - new { key = "new_value" }.AsDictionary() - ); - - contextValue.Should().Be("new_value"); - } - } -} \ No newline at end of file + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action nullOnRetry = null; + + Action policy = () => Policy + .Handle() + .RetryForever(nullOnRetry); + + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } + + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action nullOnRetry = null; + + Action policy = () => Policy + .Handle() + .RetryForever(nullOnRetry); + + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } + + [Fact] + public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .RetryForever(); + + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } + + [Fact] + public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .RetryForever(); + + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } + + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); + + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } + + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } + + [Fact] + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .RetryForeverAsync(); + + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } + + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } + + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryForeverAsync(); + + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } + + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] {"Exception #1", "Exception #2", "Exception #3"}; + var retryExceptions = new List(); + + var policy = Policy + .Handle() + .RetryForever(exception => retryExceptions.Add(exception)); + + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } + + [Fact] + public void Should_call_onretry_on_each_retry_with_the_passed_context() + { + IDictionary contextData = null; + + var policy = Policy + .Handle() + .RetryForever((_, context) => contextData = context); + + policy.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } + + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + + var policy = Policy + .Handle() + .RetryForever((_, retryCount) => retryCounts.Add(retryCount)); + + policy.RaiseException(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); + + var policy = Policy + .Handle() + .RetryForever(exception => retryExceptions.Add(exception)); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + + retryExceptions.Should() + .BeEmpty(); + } + + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; + + var policy = Policy + .Handle() + .RetryForever((_, context) => contextValue = context["key"].ToString()); + + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); + + contextValue.Should().Be("original_value"); + + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); + + contextValue.Should().Be("new_value"); + } +} diff --git a/src/Polly.Specs/Retry/RetrySpecs.cs b/src/Polly.Specs/Retry/RetrySpecs.cs index 2d5f6aee1bc..e2ac616708d 100644 --- a/src/Polly.Specs/Retry/RetrySpecs.cs +++ b/src/Polly.Specs/Retry/RetrySpecs.cs @@ -6,858 +6,857 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +public class RetrySpecs { - public class RetrySpecs + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action onRetry = (_, _) => { }; - - Action policy = () => Policy - .Handle() - .Retry(-1, onRetry); - - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } - - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action nullOnRetry = null; + Action onRetry = (_, _) => { }; + + Action policy = () => Policy + .Handle() + .Retry(-1, onRetry); + + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - Action policy = () => Policy - .Handle() - .Retry(1, nullOnRetry); + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action nullOnRetry = null; - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + Action policy = () => Policy + .Handle() + .Retry(1, nullOnRetry); - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action onRetry = (_, _, _) => { }; + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - Action policy = () => Policy - .Handle() - .Retry(-1, onRetry); + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action onRetry = (_, _, _) => { }; - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + Action policy = () => Policy + .Handle() + .Retry(-1, onRetry); - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action nullOnRetry = null; + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - Action policy = () => Policy - .Handle() - .Retry(1, nullOnRetry); + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action nullOnRetry = null; - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + Action policy = () => Policy + .Handle() + .Retry(1, nullOnRetry); - [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .Retry(3); + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .Retry(3); - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .Or() - .Retry(3); + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .Or() + .Retry(3); - [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .Retry(3); - - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } - - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .Or() - .Retry(3); - - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } - - [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .Retry(3); - - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .Or() - .Retry(3); - - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .Retry(); + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + [Fact] + public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .Retry(3); - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .Retry(); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } + + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .Or() + .Retry(3); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } + + [Fact] + public void Should_throw_when_specified_exception_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .Retry(3); - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Retry(); + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .Or() + .Retry(3); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .Retry(); - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .Retry(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .Retry(); - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Retry(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Retry(); - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .Retry(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .Retry(); - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - var policy = Policy - .Handle() - .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Retry(); - policy.RaiseException(3); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .Retry(); - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - var policy = Policy - .Handle() - .Retry(3, (exception, _) => retryExceptions.Add(exception)); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + var policy = Policy + .Handle() + .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + policy.RaiseException(3); - [Fact] - public void Should_call_onretry_with_a_handled_innerexception() - { - Exception passedToOnRetry = null; + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + var policy = Policy + .Handle() + .Retry(3, (exception, _) => retryExceptions.Add(exception)); - policy.RaiseException(withInner); + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_first_exception() - { - Exception passedToOnRetry = null; + [Fact] + public void Should_call_onretry_with_a_handled_innerexception() + { + Exception passedToOnRetry = null; - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - Exception aggregateException = new AggregateException( - new Exception("First: With Inner Exception", - toRaiseAsInner), - new Exception("Second: Without Inner Exception")); + policy.RaiseException(withInner); - policy.RaiseException(aggregateException); + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_first_exception() + { + Exception passedToOnRetry = null; - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_second_exception() - { - Exception passedToOnRetry = null; + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + Exception toRaiseAsInner = new DivideByZeroException(); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception aggregateException = new AggregateException( + new Exception("First: With Inner Exception", + toRaiseAsInner), + new Exception("Second: Without Inner Exception")); - Exception aggregateException = new AggregateException( - new Exception("First: Without Inner Exception"), - new Exception("Second: With Inner Exception", - toRaiseAsInner)); + policy.RaiseException(aggregateException); - policy.RaiseException(aggregateException); + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_second_exception() + { + Exception passedToOnRetry = null; - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_first_exception() - { - Exception passedToOnRetry = null; + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + Exception toRaiseAsInner = new DivideByZeroException(); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception aggregateException = new AggregateException( + new Exception("First: Without Inner Exception"), + new Exception("Second: With Inner Exception", + toRaiseAsInner)); - Exception aggregateException = new AggregateException( - new AggregateException( - new Exception("First: With Inner Exception", - toRaiseAsInner), - new Exception("Second: Without Inner Exception")), - new Exception("Exception")); + policy.RaiseException(aggregateException); - policy.RaiseException(aggregateException); + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_first_exception() + { + Exception passedToOnRetry = null; - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_second_exception() - { - Exception passedToOnRetry = null; + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + Exception toRaiseAsInner = new DivideByZeroException(); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception aggregateException = new AggregateException( + new AggregateException( + new Exception("First: With Inner Exception", + toRaiseAsInner), + new Exception("Second: Without Inner Exception")), + new Exception("Exception")); - Exception aggregateException = new AggregateException( - new Exception("Exception"), - new AggregateException( - new Exception("First: Without Inner Exception"), - new Exception("Second: With Inner Exception", - toRaiseAsInner))); + policy.RaiseException(aggregateException); - policy.RaiseException(aggregateException); + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_second_exception() + { + Exception passedToOnRetry = null; - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - var policy = Policy - .Handle() - .Retry((_, retryCount) => retryCounts.Add(retryCount)); + Exception toRaiseAsInner = new DivideByZeroException(); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + Exception aggregateException = new AggregateException( + new Exception("Exception"), + new AggregateException( + new Exception("First: Without Inner Exception"), + new Exception("Second: With Inner Exception", + toRaiseAsInner))); - retryCounts.Should() - .BeEmpty(); - } + policy.RaiseException(aggregateException); - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextData = context); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - policy.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); + var policy = Policy + .Handle() + .Retry((_, retryCount) => retryCounts.Add(retryCount)); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + retryCounts.Should() + .BeEmpty(); + } - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextData = context); + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - policy.Invoking(p => p.ExecuteAndCapture(_ => { throw new DivideByZeroException();}, - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextData = context); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + policy.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Policy policy = Policy - .Handle() - .Retry((_, _, context) => capturedContext = context); + [Fact] + public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - policy.RaiseException(); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextData = context); - capturedContext.Should() - .BeEmpty(); - } + policy.Invoking(p => p.ExecuteAndCapture(_ => { throw new DivideByZeroException();}, + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextValue = context["key"].ToString()); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => capturedContext = context); - contextValue.Should().Be("original_value"); + policy.RaiseException(); - policy.RaiseException( - new { key = "new_value" }.AsDictionary() - ); + capturedContext.Should() + .BeEmpty(); + } - contextValue.Should().Be("new_value"); - } + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - [Fact] - public void Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextValue = context["key"].ToString()); - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextValue = context["key"].ToString()); + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); - policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), - new { key = "original_value" }.AsDictionary())) - .Should().NotThrow(); + contextValue.Should().Be("original_value"); - contextValue.Should().Be("original_value"); + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); - policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), - new { key = "new_value" }.AsDictionary())) - .Should().NotThrow(); + contextValue.Should().Be("new_value"); + } - contextValue.Should().Be("new_value"); - } + [Fact] + public void Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .Retry(); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextValue = context["key"].ToString()); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); + policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), + new { key = "original_value" }.AsDictionary())) + .Should().NotThrow(); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + contextValue.Should().Be("original_value"); - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - bool retryInvoked = false; + policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), + new { key = "new_value" }.AsDictionary())) + .Should().NotThrow(); - Action onRetry = (_, _) => { retryInvoked = true; }; + contextValue.Should().Be("new_value"); + } - var policy = Policy - .Handle() - .Retry(0, onRetry); + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); - retryInvoked.Should().BeFalse(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_context() - { - bool retryInvoked = false; + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_without_context() + { + bool retryInvoked = false; - Action onRetry = (_, _, _) => { retryInvoked = true; }; + Action onRetry = (_, _) => { retryInvoked = true; }; - var policy = Policy - .Handle() - .Retry(0, onRetry); + var policy = Policy + .Handle() + .Retry(0, onRetry); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - retryInvoked.Should().BeFalse(); - } + retryInvoked.Should().BeFalse(); + } + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_context() + { + bool retryInvoked = false; - #region Sync cancellation tests + Action onRetry = (_, _, _) => { retryInvoked = true; }; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .Retry(3); + var policy = Policy + .Handle() + .Retry(0, onRetry); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + retryInvoked.Should().BeFalse(); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + #region Sync cancellation tests - attemptsInvoked.Should().Be(1); - } + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .Retry(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(1 + 3); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(0); + } + + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .Retry(3, (_, _) => - { - cancellationTokenSource.Cancel(); - }); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .Retry(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .Retry(3); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + result.Should().BeTrue(); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().Be(null); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - #endregion + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + result.Should().Be(null); + + attemptsInvoked.Should().Be(1); } -} \ No newline at end of file + + #endregion + + +} diff --git a/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs b/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs index 35edcb851b7..4ecd5afba40 100644 --- a/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs +++ b/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs @@ -3,196 +3,195 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +public class RetryTResultMixedResultExceptionSpecs { - public class RetryTResultMixedResultExceptionSpecs + [Fact] + public void Should_handle_exception_when_TResult_policy_handling_exceptions_only() + { + Policy policy = Policy + .Handle().Retry(1); + + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(new DivideByZeroException(), ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_unhandled_exception_when_TResult_policy_handling_exceptions_only() + { + Policy policy = Policy + .Handle().Retry(1); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_results_before_exceptions() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .Retry(2); + + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_exception_before_result() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Retry(2); + + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_exceptions_first() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Retry(4); + + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_results_first() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Retry(4); + + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Retry(3); + + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } + + [Fact] + public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() { - [Fact] - public void Should_handle_exception_when_TResult_policy_handling_exceptions_only() - { - Policy policy = Policy - .Handle().Retry(1); - - ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(new DivideByZeroException(), ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_throw_unhandled_exception_when_TResult_policy_handling_exceptions_only() - { - Policy policy = Policy - .Handle().Retry(1); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_results_before_exceptions() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .Retry(2); - - ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_exception_before_result() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Retry(2); - - ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_exceptions_first() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Retry(4); - - ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_results_first() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Retry(4); - - ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Retry(3); - - ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } - - [Fact] - public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Retry(3); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); - - ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } - - [Fact] - public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_return_unhandled_result_if_not_one_of_results_or_exceptions_specified() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .Retry(2); - - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain); - result.Should().Be(ResultPrimitive.FaultAgain); - } - - [Fact] - public void Should_throw_if_not_one_of_results_or_exceptions_handled() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Retry(2); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_handle_both_exceptions_and_specified_results_with_predicates() - { - Policy policy = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(2); - - ResultClass result = policy.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message","key"), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_throw_if_exception_predicate_not_matched() - { - Policy policy = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(2); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message", "value"), new ResultClass(ResultPrimitive.Good))) - .Should().Throw(); - } - - [Fact] - public void Should_return_unhandled_result_if_result_predicate_not_matched() - { - Policy policy = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(2); - - ResultClass result = policy.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"), new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); - } + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Retry(3); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); + + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } + + [Fact] + public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_return_unhandled_result_if_not_one_of_results_or_exceptions_specified() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .Retry(2); + + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain); + result.Should().Be(ResultPrimitive.FaultAgain); + } + + [Fact] + public void Should_throw_if_not_one_of_results_or_exceptions_handled() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Retry(2); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_handle_both_exceptions_and_specified_results_with_predicates() + { + Policy policy = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(2); + + ResultClass result = policy.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message","key"), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_if_exception_predicate_not_matched() + { + Policy policy = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(2); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message", "value"), new ResultClass(ResultPrimitive.Good))) + .Should().Throw(); + } + + [Fact] + public void Should_return_unhandled_result_if_result_predicate_not_matched() + { + Policy policy = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(2); + + ResultClass result = policy.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"), new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); } } diff --git a/src/Polly.Specs/Retry/RetryTResultSpecs.cs b/src/Polly.Specs/Retry/RetryTResultSpecs.cs index 4cd5bfd5abb..b51cb3ca968 100644 --- a/src/Polly.Specs/Retry/RetryTResultSpecs.cs +++ b/src/Polly.Specs/Retry/RetryTResultSpecs.cs @@ -9,740 +9,739 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +public class RetryTResultSpecs { - public class RetryTResultSpecs + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action, int> onRetry = (_, _) => { }; + Action, int> onRetry = (_, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action, int> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action, int> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action, int, Context> onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action, int, Context> onRetry = (_, _, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action, int, Context> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action, int, Context> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + [Fact] + public void Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. + } - [Fact] - public void Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); + [Fact] + public void Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public void Should_return_result_when_result_is_not_the_specified_handled_result() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(); + [Fact] + public void Should_return_result_when_result_is_not_the_specified_handled_result() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public void Should_return_result_when_result_is_not_one_of_the_specified_handled_results() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(); + [Fact] + public void Should_return_result_when_result_is_not_one_of_the_specified_handled_results() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(); - ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultYetAgain); - } + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public void Should_return_result_when_specified_result_predicate_is_not_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(); + [Fact] + public void Should_return_result_when_specified_result_predicate_is_not_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(); - ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); - } + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public void Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .Retry(); + [Fact] + public void Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .Retry(); - ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); - } + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public void Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(); + [Fact] + public void Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(); - ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .Retry(); + [Fact] + public void Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .Retry(); - ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_handled_result() - { - var expectedFaults = new [] { "Fault #1", "Fault #2", "Fault #3" }; - var retryFaults = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_handled_result() + { + var expectedFaults = new [] { "Fault #1", "Fault #2", "Fault #3" }; + var retryFaults = new List(); - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); - IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); - resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); + IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); + resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); - policy.RaiseResultSequence(resultsToRaise); + policy.RaiseResultSequence(resultsToRaise); - retryFaults - .Should() - .ContainInOrder(expectedFaults); - } + retryFaults + .Should() + .ContainInOrder(expectedFaults); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, retryCount) => retryCounts.Add(retryCount)); + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, retryCount) => retryCounts.Add(retryCount)); - policy.RaiseResultSequence(ResultPrimitive.Good); + policy.RaiseResultSequence(ResultPrimitive.Good); - retryCounts.Should() - .BeEmpty(); - } + retryCounts.Should() + .BeEmpty(); + } - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextData = context); + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextData = context); - policy.RaiseResultSequence( - new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ) - .Should().Be(ResultPrimitive.Good); + policy.RaiseResultSequence( + new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ) + .Should().Be(ResultPrimitive.Good); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; - - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextData = context); - - PolicyResult result = policy.RaiseResultSequenceOnExecuteAndCapture( - new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(ResultPrimitive), - Result = ResultPrimitive.Good - }); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } - - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; + [Fact] + public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => capturedContext = context); + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextData = context); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + PolicyResult result = policy.RaiseResultSequenceOnExecuteAndCapture( + new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - capturedContext.Should() - .BeEmpty(); - } + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(ResultPrimitive), + Result = ResultPrimitive.Good + }); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextValue = context["key"].ToString()); + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => capturedContext = context); - policy.RaiseResultSequence( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - contextValue.Should().Be("original_value"); + capturedContext.Should() + .BeEmpty(); + } - policy.RaiseResultSequence( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - contextValue.Should().Be("new_value"); - } + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextValue = context["key"].ToString()); - [Fact] - public void Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + policy.RaiseResultSequence( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextValue = context["key"].ToString()); + contextValue.Should().Be("original_value"); - policy.RaiseResultSequenceOnExecuteAndCapture( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + policy.RaiseResultSequence( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("new_value"); + } - policy.RaiseResultSequenceOnExecuteAndCapture( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + [Fact] + public void Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - contextValue.Should().Be("new_value"); - } + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextValue = context["key"].ToString()); - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(1); + policy.RaiseResultSequenceOnExecuteAndCapture( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); + contextValue.Should().Be("original_value"); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); + policy.RaiseResultSequenceOnExecuteAndCapture( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - bool retryInvoked = false; + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(1); + + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); + + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); - Action, int> onRetry = (_, _) => { retryInvoked = true; }; + } - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(0, onRetry); + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_without_context() + { + bool retryInvoked = false; - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); + Action, int> onRetry = (_, _) => { retryInvoked = true; }; - retryInvoked.Should().BeFalse(); - } + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(0, onRetry); - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_context() - { - bool retryInvoked = false; + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); + + retryInvoked.Should().BeFalse(); + } - Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_context() + { + bool retryInvoked = false; + + Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; + + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(0, onRetry); + + policy.RaiseResultSequence( + new { key = "value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); + + retryInvoked.Should().BeFalse(); + } - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(0, onRetry); + #region Sync cancellation tests - policy.RaiseResultSequence( - new { key = "value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - retryInvoked.Should().BeFalse(); - } + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - #region Sync cancellation tests + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(2); - } - - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(2); - } - - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1 + 3); - } - - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(2); + } + + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; - - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Fault); - - attemptsInvoked.Should().Be(1 + 3); - } - - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(2); + } + + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - RetryPolicy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3, (_, _) => - { - cancellationTokenSource.Cancel(); - }); + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + { + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - #endregion + Scenario scenario = new Scenario + { + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Fault); + + attemptsInvoked.Should().Be(1 + 3); } -} \ No newline at end of file + + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + RetryPolicy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + #endregion + +} diff --git a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs index 8d03d0b7110..d937e3e694a 100644 --- a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs +++ b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs @@ -10,752 +10,751 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +public class RetryTResultSpecsAsync { - public class RetryTResultSpecsAsync + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action, int> onRetry = (_, _) => { }; + Action, int> onRetry = (_, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action, int> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action, int> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action, int, Context> onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action, int, Context> onRetry = (_, _, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action, int, Context> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action, int, Context> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public async Task Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(3); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(3); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + [Fact] + public async Task Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. + } - [Fact] - public async Task Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(3); + [Fact] + public async Task Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(3); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public async Task Should_return_result_when_result_is_not_the_specified_handled_result() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_result_is_not_the_specified_handled_result() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public async Task Should_return_result_when_result_is_not_one_of_the_specified_handled_results() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_result_is_not_one_of_the_specified_handled_results() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(); - ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultYetAgain); - } + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public async Task Should_return_result_when_specified_result_predicate_is_not_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_specified_result_predicate_is_not_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .RetryAsync(); - ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); - } + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public async Task Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .RetryAsync(); - ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); - } + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public async Task Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .RetryAsync(); + [Fact] + public async Task Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .RetryAsync(); - ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .RetryAsync(); + [Fact] + public async Task Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .RetryAsync(); - ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_handled_result() - { - var expectedFaults = new[] { "Fault #1", "Fault #2", "Fault #3" }; - var retryFaults = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_handled_result() + { + var expectedFaults = new[] { "Fault #1", "Fault #2", "Fault #3" }; + var retryFaults = new List(); - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .RetryAsync(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .RetryAsync(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); - IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); - resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); + IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); + resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); - (await policy.RaiseResultSequenceAsync(resultsToRaise)) - .ResultCode.Should().Be(ResultPrimitive.Fault); + (await policy.RaiseResultSequenceAsync(resultsToRaise)) + .ResultCode.Should().Be(ResultPrimitive.Fault); - retryFaults - .Should() - .ContainInOrder(expectedFaults); - } + retryFaults + .Should() + .ContainInOrder(expectedFaults); + } - [Fact] - public async Task Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + [Fact] + public async Task Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - retryCounts.Should() - .BeEmpty(); - } + retryCounts.Should() + .BeEmpty(); + } - [Fact] - public async Task Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public async Task Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextData = context); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextData = context); - (await policy.RaiseResultSequenceAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - )) - .Should().Be(ResultPrimitive.Good); + (await policy.RaiseResultSequenceAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + )) + .Should().Be(ResultPrimitive.Good); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } + + [Fact] + public async Task Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextData = context); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + PolicyResult result = await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - [Fact] - public async Task Should_call_onretry_with_the_passed_context_when_execute_and_capture() + result.Should().BeEquivalentTo(new { - IDictionary contextData = null; + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(ResultPrimitive), + Result = ResultPrimitive.Good + }); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextData = context); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - PolicyResult result = await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(ResultPrimitive), - Result = ResultPrimitive.Good - }); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => capturedContext = context); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; + capturedContext.Should() + .BeEmpty(); + } - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => capturedContext = context); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - capturedContext.Should() - .BeEmpty(); - } + await policy.RaiseResultSequenceAsync( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + contextValue.Should().Be("original_value"); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); + await policy.RaiseResultSequenceAsync( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - await policy.RaiseResultSequenceAsync( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + contextValue.Should().Be("new_value"); + } - contextValue.Should().Be("original_value"); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - await policy.RaiseResultSequenceAsync( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - contextValue.Should().Be("new_value"); - } + await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + contextValue.Should().Be("original_value"); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); + await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + contextValue.Should().Be("new_value"); + } - contextValue.Should().Be("original_value"); + [Fact] + public async Task Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(1); - await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); - contextValue.Should().Be("new_value"); - } + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); - [Fact] - public async Task Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(1); + } - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); + [Fact] + public async Task Should_not_call_onretry_when_retry_count_is_zero_without_context() + { + bool retryInvoked = false; - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); + Action, int> onRetry = (_, _) => { retryInvoked = true; }; - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(0, onRetry); - [Fact] - public async Task Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - bool retryInvoked = false; + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); - Action, int> onRetry = (_, _) => { retryInvoked = true; }; + retryInvoked.Should().BeFalse(); + } - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(0, onRetry); + [Fact] + public async Task Should_not_call_onretry_when_retry_count_is_zero_with_context() + { + bool retryInvoked = false; - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); + Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; - retryInvoked.Should().BeFalse(); - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(0, onRetry); - [Fact] - public async Task Should_not_call_onretry_when_retry_count_is_zero_with_context() - { - bool retryInvoked = false; + (await policy.RaiseResultSequenceAsync( + new { key = "value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); - Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; + retryInvoked.Should().BeFalse(); + } - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(0, onRetry); + #region Async and cancellation tests - (await policy.RaiseResultSequenceAsync( - new { key = "value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - retryInvoked.Should().BeFalse(); - } + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - #region Async and cancellation tests + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; - [Fact] - public async Task Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(async (_, _) => + { + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); - TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + (await policy.ExecuteAsync(async () => + { + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + return ResultPrimitive.Fault; + })).Should().Be(ResultPrimitive.Fault); - int executeDelegateInvocations = 0; - int executeDelegateInvocationsWhenOnRetryExits = 0; + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(async (_, _) => - { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - (await policy.ExecuteAsync(async () => - { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - return ResultPrimitive.Fault; - })).Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; - - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; - - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; - - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; - - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(2); - } - - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; - - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(2); - } - - [Fact] - public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; - - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1 + 3); - } - - [Fact] - public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + [Fact] + public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; + + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + attemptsInvoked.Should().Be(1 + 3); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; - - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Fault); - - attemptsInvoked.Should().Be(1 + 3); - } - - [Fact] - public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3, (_, _) => - { - cancellationTokenSource.Cancel(); - }); + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Fault); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - Scenario scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; - - var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - #endregion + [Fact] + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + Scenario scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; + + var ex = await policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); } + + #endregion } diff --git a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs index 85ec50b620a..596e0c378da 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs @@ -13,1098 +13,1097 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetryAsyncSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetryAsyncSpecs : IDisposable + public WaitAndRetryAsyncSpecs() { - public WaitAndRetryAsyncSpecs() - { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - [Fact] - public void Should_throw_when_sleep_durations_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_sleep_durations_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty(), nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public async Task Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public async Task Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public async Task Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public async Task Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public async Task Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty()); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(Enumerable.Empty()); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .WaitAndRetryAsync(Enumerable.Empty()); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetryAsync(Enumerable.Empty()); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var totalTimeSlept = 0; + + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - [Fact] - public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + SystemClock.SleepAsync = (span, _) => { - var totalTimeSlept = 0; + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + await policy.RaiseExceptionAsync(3); - SystemClock.SleepAsync = (span, _) => - { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; - }; + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - await policy.RaiseExceptionAsync(3); + [Fact] + public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() + { + var totalTimeSlept = 0; - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - [Fact] - public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() + SystemClock.SleepAsync = (span, _) => { - var totalTimeSlept = 0; + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().ThrowAsync(); - SystemClock.SleepAsync = (span, _) => - { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; - }; + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - await policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().ThrowAsync(); + [Fact] + public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var totalTimeSlept = 0; - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - [Fact] - public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + SystemClock.SleepAsync = (span, _) => { - var totalTimeSlept = 0; + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + await policy.RaiseExceptionAsync(2); - SystemClock.SleepAsync = (span, _) => - { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; - }; + totalTimeSlept.Should() + .Be(1 + 2); + } - await policy.RaiseExceptionAsync(2); + [Fact] + public async Task Should_not_sleep_if_no_retries() + { + var totalTimeSlept = 0; - totalTimeSlept.Should() - .Be(1 + 2); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty()); - [Fact] - public async Task Should_not_sleep_if_no_retries() + SystemClock.SleepAsync = (span, _) => { - var totalTimeSlept = 0; + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; + + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty()); + totalTimeSlept.Should() + .Be(0); + } - SystemClock.SleepAsync = (span, _) => + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_timespan() + { + var expectedRetryWaits = new [] { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; + 1.Seconds(), + 2.Seconds(), + 3.Seconds() }; - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + var actualRetryWaits = new List(); - totalTimeSlept.Should() - .Be(0); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_timespan() - { - var expectedRetryWaits = new [] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }; + await policy.RaiseExceptionAsync(3); - var actualRetryWaits = new List(); + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - await policy.RaiseExceptionAsync(3); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (exception, _) => retryExceptions.Add(exception)); - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (exception, _) => retryExceptions.Add(exception)); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + await policy.RaiseExceptionAsync(3); - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); + [Fact] + public async Task Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); - await policy.RaiseExceptionAsync(3); + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - [Fact] - public async Task Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); + retryExceptions.Should().BeEmpty(); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); + [Fact] + public async Task Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); - retryExceptions.Should().BeEmpty(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public async Task Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, context) => contextData = context); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + await policy.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); - [Fact] - public async Task Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, context) => contextData = context); - - await policy.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_data() + { + Context capturedContext = null; - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_data() - { - Context capturedContext = null; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, context) => capturedContext = context); + await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); + + capturedContext.Should() + .BeEmpty(); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, context) => capturedContext = context); - await policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrowAsync(); - - capturedContext.Should() - .BeEmpty(); - } + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }, + (_, _, context) => contextValue = context["key"].ToString()); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }, - (_, _, context) => contextValue = context["key"].ToString()); + await policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - await policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); + contextValue.Should().Be("original_value"); - contextValue.Should().Be("original_value"); + await policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() + ); - await policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + contextValue.Should().Be("new_value"); + } - contextValue.Should().Be("new_value"); - } + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action onRetry = (_, _) => { }; - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action onRetry = (_, _) => { }; + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(-1, _ => TimeSpan.Zero, onRetry); - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(-1, _ => TimeSpan.Zero, onRetry); + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(1, null, onRetry); - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(1, null, onRetry); + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() + { + Action nullOnRetry = null; - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() - { - Action nullOnRetry = null; + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(1, _ => TimeSpan.Zero, nullOnRetry); - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(1, _ => TimeSpan.Zero, nullOnRetry); + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + [Fact] + public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() + { + var expectedRetryWaits = new[] + { + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() + }; - [Fact] - public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] - { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetryAsync(5, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) - ); - - await policy.RaiseExceptionAsync(5); - - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + var actualRetryWaits = new List(); - [Fact] - public async Task Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() - { - object capturedExceptionInstance = null; + var policy = Policy + .Handle() + .WaitAndRetryAsync(5, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); - DivideByZeroException exceptionInstance = new DivideByZeroException(); + await policy.RaiseExceptionAsync(5); - var policy = Policy - .Handle() - .WaitAndRetryAsync(5, - sleepDurationProvider:( _, ex, _) => - { - capturedExceptionInstance = ex; - return TimeSpan.FromMilliseconds(0); - }, - onRetryAsync: (_, _, _, _) => TaskHelper.EmptyTask); + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - await policy.RaiseExceptionAsync(exceptionInstance); + [Fact] + public async Task Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() + { + object capturedExceptionInstance = null; - capturedExceptionInstance.Should().Be(exceptionInstance); - } + DivideByZeroException exceptionInstance = new DivideByZeroException(); - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ + var policy = Policy + .Handle() + .WaitAndRetryAsync(5, + sleepDurationProvider:( _, ex, _) => + { + capturedExceptionInstance = ex; + return TimeSpan.FromMilliseconds(0); + }, + onRetryAsync: (_, _, _, _) => TaskHelper.EmptyTask); - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, - }; + await policy.RaiseExceptionAsync(exceptionInstance); + + capturedExceptionInstance.Should().Be(exceptionInstance); + } - var actualRetryWaits = new List(); + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ - var policy = Policy - .Handle() - .WaitAndRetryAsync(2, - sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], - onRetryAsync: (_, timeSpan, _, _) => - { - actualRetryWaits.Add(timeSpan); - return TaskHelper.EmptyTask; - }); + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(() => { - if (enumerator.MoveNext()) throw enumerator.Current.Key; + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryAsync(2, + sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], + onRetryAsync: (_, timeSpan, _, _) => + { + actualRetryWaits.Add(timeSpan); return TaskHelper.EmptyTask; }); - } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } - - [Fact] - public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + using (var enumerator = expectedRetryWaits.GetEnumerator()) { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; - - TimeSpan defaultRetryAfter = 30.Seconds(); + await policy.ExecuteAsync(() => { + if (enumerator.MoveNext()) throw enumerator.Current.Key; + return TaskHelper.EmptyTask; + }); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(1, - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } - bool failedOnce = false; - await policy.ExecuteAsync(async (context, _) => - { - await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. - context["RetryAfter"] = expectedRetryDuration; + [Fact] + public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; - if (!failedOnce) - { - failedOnce = true; - throw new DivideByZeroException(); - } - }, - new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. - CancellationToken.None - ); + TimeSpan defaultRetryAfter = 30.Seconds(); - actualRetryDuration.Should().Be(expectedRetryDuration); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(1, + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); - [Fact] - public async Task Should_not_call_onretry_when_retry_count_is_zero() + bool failedOnce = false; + await policy.ExecuteAsync(async (context, _) => { - bool retryInvoked = false; + await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. + context["RetryAfter"] = expectedRetryDuration; - Action onRetry = (_, _) => { retryInvoked = true; }; + if (!failedOnce) + { + failedOnce = true; + throw new DivideByZeroException(); + } + }, + new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. + CancellationToken.None + ); - var policy = Policy - .Handle() - .WaitAndRetryAsync(0, _ => TimeSpan.FromSeconds(1), onRetry); + actualRetryDuration.Should().Be(expectedRetryDuration); + } - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + [Fact] + public async Task Should_not_call_onretry_when_retry_count_is_zero() + { + bool retryInvoked = false; - retryInvoked.Should().BeFalse(); - } + Action onRetry = (_, _) => { retryInvoked = true; }; - [Fact] - public async Task Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + var policy = Policy + .Handle() + .WaitAndRetryAsync(0, _ => TimeSpan.FromSeconds(1), onRetry); - TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - int executeDelegateInvocations = 0; - int executeDelegateInvocationsWhenOnRetryExits = 0; + retryInvoked.Should().BeFalse(); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(1, - _ => TimeSpan.Zero, - async (_, _) => - { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + [Fact] + public async Task Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - throw new DivideByZeroException(); - })).Should().ThrowAsync(); + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(1, + _ => TimeSpan.Zero, + async (_, _) => + { + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + await policy.Awaiting(p => p.ExecuteAsync(async () => { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + throw new DivideByZeroException(); + })).Should().ThrowAsync(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, - }; + [Fact] + public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(1 + 3); + } - cancellationTokenSource.Cancel(); + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(0); + } - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - attemptsInvoked.Should().Be(1); - } + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() + Scenario scenario = new Scenario { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - SystemClock.SleepAsync = Task.Delay; + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); - TimeSpan shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; + attemptsInvoked.Should().Be(1 + 3); + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { retryDelay }); + [Fact] + public async Task Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + SystemClock.SleepAsync = Task.Delay; - Stopwatch watch = new Stopwatch(); - watch.Start(); + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 1, - AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { retryDelay }); - cancellationTokenSource.CancelAfter(shimTimeSpan); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); - watch.Stop(); + Stopwatch watch = new Stopwatch(); + watch.Start(); - attemptsInvoked.Should().Be(1); + Scenario scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 1, + AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. + ActionObservesCancellation = false + }; - watch.Elapsed.Should().BeLessThan(retryDelay); - watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: TimeSpan.FromMilliseconds((int)shimTimeSpan.TotalMilliseconds / 2)); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. - } + cancellationTokenSource.CancelAfter(shimTimeSpan); - [Fact] - public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + watch.Stop(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, - (_, _) => - { - cancellationTokenSource.Cancel(); - }); + attemptsInvoked.Should().Be(1); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + watch.Elapsed.Should().BeLessThan(retryDelay); + watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: TimeSpan.FromMilliseconds((int)shimTimeSpan.TotalMilliseconds / 2)); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + [Fact] + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, + (_, _) => + { + cancellationTokenSource.Cancel(); + }); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null - }; + [Fact] + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - await policy.Awaiting(action) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public async Task Should_honour_and_report_cancellation_during_func_execution() + Scenario scenario = new Scenario { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + await policy.Awaiting(action) + .Should().NotThrowAsync(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + result.Should().BeTrue(); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - var ex = await policy.Awaiting(action).Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().Be(null); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - public void Dispose() + Scenario scenario = new Scenario { - SystemClock.Reset(); - } + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + + result.Should().Be(null); + + attemptsInvoked.Should().Be(1); + } + + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs index 9abd93b3d1c..4b3f19b8f39 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs @@ -12,703 +12,702 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetryForeverAsyncSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetryForeverAsyncSpecs : IDisposable + public WaitAndRetryForeverAsyncSpecs() { - public WaitAndRetryForeverAsyncSpecs() - { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; + Func provider = _ => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context() - { - Action nullOnRetry = null; - Func provider = (_, _) => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context() + { + Action nullOnRetry = null; + Func provider = (_, _) => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public async Task Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); + [Fact] + public async Task Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); + [Fact] + public async Task Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); - await policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForeverAsync(provider); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public async Task Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle(_ => false) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => false) + .WaitAndRetryForeverAsync(provider); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public async Task Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetryForeverAsync(provider); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - Func provider = _ => 1.Seconds(); + [Fact] + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + Func provider = _ => 1.Seconds(); - var policy = Policy - .Handle(_ => true) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => true) + .WaitAndRetryForeverAsync(provider); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => 1.Seconds(); + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => 1.Seconds(); - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryForeverAsync(provider); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - [Fact] - public async Task Should_not_sleep_if_no_retries() - { - Func provider = _ => 1.Seconds(); + [Fact] + public async Task Should_not_sleep_if_no_retries() + { + Func provider = _ => 1.Seconds(); - var totalTimeSlept = 0; + var totalTimeSlept = 0; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - totalTimeSlept.Should() - .Be(0); - } + totalTimeSlept.Should() + .Be(0); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); - Func provider = _ => TimeSpan.Zero; + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); - policy.RaiseExceptionAsync(3); + policy.RaiseExceptionAsync(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public async Task Should_not_call_onretry_when_no_retries_are_performed() - { - Func provider = _ => 1.Seconds(); - var retryExceptions = new List(); + [Fact] + public async Task Should_not_call_onretry_when_no_retries_are_performed() + { + Func provider = _ => 1.Seconds(); + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); - retryExceptions.Should().BeEmpty(); - } + retryExceptions.Should().BeEmpty(); + } - [Fact] - public void Should_create_new_context_for_each_call_to_policy() - { - Func provider = (_, _) => 1.Seconds(); + [Fact] + public void Should_create_new_context_for_each_call_to_policy() + { + Func provider = (_, _) => 1.Seconds(); - string contextValue = null; + string contextValue = null; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - provider, - (_, _, context) => contextValue = context["key"].ToString()); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + provider, + (_, _, context) => contextValue = context["key"].ToString()); - policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() + ); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] - { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) - ); - - await policy.RaiseExceptionAsync(5); - - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + [Fact] + public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() + { + var expectedRetryWaits = new[] + { + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() + }; - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ + var actualRetryWaits = new List(); - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); - var actualRetryWaits = new List(); + await policy.RaiseExceptionAsync(5); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], - onRetryAsync: (_, timeSpan, _) => - { - actualRetryWaits.Add(timeSpan); - return TaskHelper.EmptyTask; - }); + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(() => { - if (enumerator.MoveNext()) throw enumerator.Current.Key; + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], + onRetryAsync: (_, timeSpan, _) => + { + actualRetryWaits.Add(timeSpan); return TaskHelper.EmptyTask; }); - } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(() => { + if (enumerator.MoveNext()) throw enumerator.Current.Key; + return TaskHelper.EmptyTask; + }); } + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } - [Fact] - public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() - { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; - - TimeSpan defaultRetryAfter = 30.Seconds(); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + [Fact] + public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; - bool failedOnce = false; - await policy.ExecuteAsync(async (context, _) => - { - await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. - context["RetryAfter"] = expectedRetryDuration; + TimeSpan defaultRetryAfter = 30.Seconds(); - if (!failedOnce) - { - failedOnce = true; - throw new DivideByZeroException(); - } - }, - new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. - CancellationToken.None - ); - - actualRetryDuration.Should().Be(expectedRetryDuration); - } + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); - [Fact] - public async Task Should_wait_asynchronously_for_async_onretry_delegate() + bool failedOnce = false; + await policy.ExecuteAsync(async (context, _) => { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. + context["RetryAfter"] = expectedRetryDuration; - TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + if (!failedOnce) + { + failedOnce = true; + throw new DivideByZeroException(); + } + }, + new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. + CancellationToken.None + ); - int executeDelegateInvocations = 0; - int executeDelegateInvocationsWhenOnRetryExits = 0; + actualRetryDuration.Should().Be(expectedRetryDuration); + } - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - _ => TimeSpan.Zero, - async (_, _) => - { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + [Fact] + public async Task Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } - })).Should().NotThrowAsync(); + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + _ => TimeSpan.Zero, + async (_, _) => + { + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + await policy.Awaiting(p => p.ExecuteAsync(async () => { - Func provider = _ => TimeSpan.Zero; + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } + })).Should().NotThrowAsync(); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrowAsync(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + attemptsInvoked.Should().Be(1); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - cancellationTokenSource.Cancel(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } - - [Fact] - public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + cancellationTokenSource.Cancel(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + [Fact] + public async Task Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + Func provider = _ => TimeSpan.Zero; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - attemptsInvoked.Should().Be(1); - } + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + attemptsInvoked.Should().Be(1); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + attemptsInvoked.Should().Be(1); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + attemptsInvoked.Should().Be(1); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + attemptsInvoked.Should().Be(2); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, - (_, _) => - { - cancellationTokenSource.Cancel(); - }); + attemptsInvoked.Should().Be(2); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, + (_, _) => + { + cancellationTokenSource.Cancel(); + }); - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var ex = await policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + attemptsInvoked.Should().Be(1); + } - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + [Fact] + public async Task Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + Func provider = _ => TimeSpan.Zero; - bool? result = null; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; - - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - await policy.Awaiting(action).Should().NotThrowAsync(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - result.Should().BeTrue(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public async Task Should_honour_and_report_cancellation_during_func_execution() + Scenario scenario = new Scenario { - Func provider = _ => TimeSpan.Zero; + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + await policy.Awaiting(action).Should().NotThrowAsync(); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + result.Should().BeTrue(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - bool? result = null; + [Fact] + public async Task Should_honour_and_report_cancellation_during_func_execution() + { + Func provider = _ => TimeSpan.Zero; - Scenario scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - var ex = await policy.Awaiting(action).Should().ThrowAsync(); - ex.And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - result.Should().Be(null); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - public void Dispose() + Scenario scenario = new Scenario { - SystemClock.Reset(); - } + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + var ex = await policy.Awaiting(action).Should().ThrowAsync(); + ex.And.CancellationToken.Should().Be(cancellationToken); + + result.Should().Be(null); + + attemptsInvoked.Should().Be(1); + } + + public void Dispose() + { + SystemClock.Reset(); } } diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs index cbd9a4752ea..739e4c82d9c 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs @@ -7,364 +7,363 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetryForeverSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetryForeverSpecs : IDisposable + public WaitAndRetryForeverSpecs() { - public WaitAndRetryForeverSpecs() - { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - Func sleepDurationProvider = null; + Func sleepDurationProvider = null; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(sleepDurationProvider, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(sleepDurationProvider, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; + Func provider = _ => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context() - { - Action nullOnRetry = null; - Func provider = (_, _) => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context() + { + Action nullOnRetry = null; + Func provider = (_, _) => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .WaitAndRetryForever(_ => new TimeSpan()); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .WaitAndRetryForever(_ => new TimeSpan()); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForever(_ => new TimeSpan()); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForever(_ => new TimeSpan()); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - Func provider = _ => TimeSpan.Zero; - - var policy = Policy - .Handle() - .WaitAndRetryForever(provider); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + Func provider = _ => TimeSpan.Zero; + + var policy = Policy + .Handle() + .WaitAndRetryForever(provider); - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForever(provider); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle(_ => false) - .WaitAndRetryForever(provider); + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + Func provider = _ => TimeSpan.Zero; - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => TimeSpan.Zero; + var policy = Policy + .Handle(_ => false) + .WaitAndRetryForever(provider); - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetryForever(provider); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => TimeSpan.Zero; - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - Func provider = _ => 1.Seconds(); + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetryForever(provider); - var policy = Policy - .Handle(_ => true) - .WaitAndRetryForever(provider); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } - - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => 1.Seconds(); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + Func provider = _ => 1.Seconds(); - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryForever(provider); + var policy = Policy + .Handle(_ => true) + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } + + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => 1.Seconds(); - [Fact] - public void Should_not_sleep_if_no_retries() - { - Func provider = _ => 1.Seconds(); + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryForever(provider); - var totalTimeSlept = 0; + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - var policy = Policy - .Handle() - .WaitAndRetryForever(provider); + [Fact] + public void Should_not_sleep_if_no_retries() + { + Func provider = _ => 1.Seconds(); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + var totalTimeSlept = 0; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + var policy = Policy + .Handle() + .WaitAndRetryForever(provider); - totalTimeSlept.Should() - .Be(0); - } + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); - Func provider = _ => TimeSpan.Zero; + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - var policy = Policy - .Handle() - .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); + totalTimeSlept.Should() + .Be(0); + } - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); + Func provider = _ => TimeSpan.Zero; - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); - Func provider = _ => TimeSpan.Zero; + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - var policy = Policy - .Handle() - .WaitAndRetryForever(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - policy.RaiseException(3); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + Func provider = _ => TimeSpan.Zero; - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - Func provider = _ => 1.Seconds(); - var retryExceptions = new List(); + policy.RaiseException(3); - var policy = Policy - .Handle() - .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + Func provider = _ => 1.Seconds(); + var retryExceptions = new List(); - retryExceptions.Should().BeEmpty(); - } + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); - [Fact] - public void Should_create_new_context_for_each_call_to_policy() - { - Func provider = (_, _) => 1.Seconds(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - string contextValue = null; + retryExceptions.Should().BeEmpty(); + } - var policy = Policy - .Handle() - .WaitAndRetryForever( - provider, - (_, _, context) => contextValue = context["key"].ToString()); + [Fact] + public void Should_create_new_context_for_each_call_to_policy() + { + Func provider = (_, _) => 1.Seconds(); - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); + string contextValue = null; - contextValue.Should().Be("original_value"); + var policy = Policy + .Handle() + .WaitAndRetryForever( + provider, + (_, _, context) => contextValue = context["key"].ToString()); - policy.RaiseException( - new { key = "new_value" }.AsDictionary() - ); + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("original_value"); - [Fact] - public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] - { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetryForever( - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) - ); - - policy.RaiseException(5); - - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ + contextValue.Should().Be("new_value"); + } - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, + [Fact] + public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() + { + var expectedRetryWaits = new[] + { + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .Handle() - .WaitAndRetryForever( - (_, exc, _) => expectedRetryWaits[exc], - (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) - ); + var policy = Policy + .Handle() + .WaitAndRetryForever( + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); - } + policy.RaiseException(5); - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - [Fact] - public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() - { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ - TimeSpan defaultRetryAfter = 30.Seconds(); + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; - var policy = Policy - .Handle() - .WaitAndRetryForever( - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan) context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + var actualRetryWaits = new List(); - bool failedOnce = false; - policy.Execute(context => - { - // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. - context["RetryAfter"] = expectedRetryDuration; - - if (!failedOnce) - { - failedOnce = true; - throw new DivideByZeroException(); - } - }, - new {RetryAfter = defaultRetryAfter}.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. - ); - - actualRetryDuration.Should().Be(expectedRetryDuration); - } + var policy = Policy + .Handle() + .WaitAndRetryForever( + (_, exc, _) => expectedRetryWaits[exc], + (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) + ); - public void Dispose() + using (var enumerator = expectedRetryWaits.GetEnumerator()) { - SystemClock.Reset(); + policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + + [Fact] + public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; + + TimeSpan defaultRetryAfter = 30.Seconds(); + + var policy = Policy + .Handle() + .WaitAndRetryForever( + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan) context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); + + bool failedOnce = false; + policy.Execute(context => + { + // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. + context["RetryAfter"] = expectedRetryDuration; + + if (!failedOnce) + { + failedOnce = true; + throw new DivideByZeroException(); + } + }, + new {RetryAfter = defaultRetryAfter}.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. + ); + + actualRetryDuration.Should().Be(expectedRetryDuration); + } + + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs index 2d0075a4fad..461b7b1cc2b 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs @@ -7,56 +7,55 @@ using Xunit; using FluentAssertions.Extensions; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetryForeverTResultAsyncSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetryForeverTResultAsyncSpecs : IDisposable + public WaitAndRetryForeverTResultAsyncSpecs() { - public WaitAndRetryForeverTResultAsyncSpecs() - { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ - - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetryForeverAsync( - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _) => - { - actualRetryWaits.Add(timeSpan); - return TaskHelper.EmptyTask; - }); - - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(async () => + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryForeverAsync( + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _) => { - await TaskHelper.EmptyTask; - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; }); - } - - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } - public void Dispose() + using (var enumerator = expectedRetryWaits.GetEnumerator()) { - SystemClock.Reset(); + await policy.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); } + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } -} \ No newline at end of file + + public void Dispose() + { + SystemClock.Reset(); + } + +} diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs index 068b22cc1c5..57507cc6927 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs @@ -6,52 +6,51 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetryForeverTResultSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetryForeverTResultSpecs : IDisposable + public WaitAndRetryForeverTResultSpecs() { - public WaitAndRetryForeverTResultSpecs() - { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetryForever( - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) - ); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryForever( + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) + ); - using (var enumerator = expectedRetryWaits.GetEnumerator()) + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => { - policy.Execute(() => - { - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; - }); - } - - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); } - public void Dispose() - { - SystemClock.Reset(); - } + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file + +} diff --git a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs index 022f45aa30b..7a9c169143c 100644 --- a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs @@ -10,1192 +10,1191 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetrySpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetrySpecs : IDisposable + public WaitAndRetrySpecs() { - public WaitAndRetrySpecs() - { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } - - [Fact] - public void Should_throw_when_sleep_durations_is_null_without_context() - { - Action onRetry = (_, _) => { }; - - Action policy = () => Policy - .Handle() - .WaitAndRetry(null, onRetry); - - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_throw_when_sleep_durations_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_durations_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } - - [Fact] - public void Should_throw_when_sleep_durations_is_null_with_attempts_with_context() - { - Action onRetry = (_, _, _, _) => { }; + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - Action policy = () => Policy - .Handle() - .WaitAndRetry(null, onRetry); + [Fact] + public void Should_throw_when_sleep_durations_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } + Action policy = () => Policy + .Handle() + .WaitAndRetry(null, onRetry); - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - Action policy = () => Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), nullOnRetry); + [Fact] + public void Should_throw_when_sleep_durations_is_null_with_attempts_with_context() + { + Action onRetry = (_, _, _, _) => { }; - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + Action policy = () => Policy + .Handle() + .WaitAndRetry(null, onRetry); - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context() - { - Action nullOnRetry = null; + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - Action policy = () => Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), nullOnRetry); + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + Action policy = () => Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), nullOnRetry); - [Fact] - public void Should_throw_when_onretry_action_is_null_with_attempts_with_context() - { - Action nullOnRetry = null; + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - Action policy = () => Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), nullOnRetry); + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context() + { + Action nullOnRetry = null; - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + Action policy = () => Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), nullOnRetry); - [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + [Fact] + public void Should_throw_when_onretry_action_is_null_with_attempts_with_context() + { + Action nullOnRetry = null; - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + Action policy = () => Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), nullOnRetry); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(2)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_then_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(2)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException(2)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_then_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException(2)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public void Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .WaitAndRetry(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types_async() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty()); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().ThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public async Task Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types_async() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().ThrowAsync(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .WaitAndRetry(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .WaitAndRetry(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetry(Enumerable.Empty()); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetry(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .WaitAndRetry(new[] + { + 1.Seconds() + }); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public async Task Should_not_throw_when_specified_exception_predicate_is_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - await policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrowAsync(); - } + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var totalTimeSlept = 0; + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetry(new[] + { + 1.Seconds() + }); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + [Fact] + public async Task Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - policy.RaiseException(3); + await policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrowAsync(); + } - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + [Fact] + public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var totalTimeSlept = 0; - [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() - { - var totalTimeSlept = 0; + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + policy.RaiseException(3); - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + [Fact] + public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() + { + var totalTimeSlept = 0; - [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var totalTimeSlept = 0; + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); - policy.RaiseException(2); + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - totalTimeSlept.Should() - .Be(1 + 2); - } + [Fact] + public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var totalTimeSlept = 0; - [Fact] - public void Should_not_sleep_if_no_retries() - { - var totalTimeSlept = 0; + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - var policy = Policy - .Handle() - .WaitAndRetry(Enumerable.Empty()); + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + policy.RaiseException(2); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + totalTimeSlept.Should() + .Be(1 + 2); + } - totalTimeSlept.Should() - .Be(0); - } + [Fact] + public void Should_not_sleep_if_no_retries() + { + var totalTimeSlept = 0; - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_timespan() - { - var expectedRetryWaits = new [] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }; + var policy = Policy + .Handle() + .WaitAndRetry(Enumerable.Empty()); - var actualRetryWaits = new List(); + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - policy.RaiseException(3); + totalTimeSlept.Should() + .Be(0); + } - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_timespan() + { + var expectedRetryWaits = new [] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }; - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + policy.RaiseException(3); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new string[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (exception, _) => retryExceptions.Add(exception)); - policy.RaiseException(3); + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.RaiseException(3); - retryExceptions.Should() - .BeEmpty(); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); + var policy = Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + retryExceptions.Should() + .BeEmpty(); + } - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, context) => contextData = context); - - policy.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds() + }); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds() - }, - (_, _, context) => contextValue = context["key"].ToString()); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - contextValue.Should().Be("original_value"); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, context) => contextData = context); - policy.RaiseException( - new { key = "new_value" }.AsDictionary() + policy.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() ); - contextValue.Should().Be("new_value"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - Action policy = () => Policy - .Handle() - .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds() + }, + (_, _, context) => contextValue = context["key"].ToString()); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action onRetry = (_, _, _) => { }; + contextValue.Should().Be("original_value"); - Action policy = () => Policy - .Handle() - .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_attempts_with_context() - { - Action onRetry = (_, _, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_attempts_with_context() + { + Action onRetry = (_, _, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, (Func) null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_attempts_with_context() - { - Action onRetry = (_, _, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, (Func)null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, (Func) null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context_when_using_provider_overload() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_attempts_with_context() + { + Action onRetry = (_, _, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, (Func)null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_attempts_with_context_when_using_provider_overload() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] - { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetry(5, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) - ); - - policy.RaiseException(5); - - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context_when_using_provider_overload() + { + Action nullOnRetry = null; - [Fact] - public void Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() - { - object capturedExceptionInstance = null; - - DivideByZeroException exceptionInstance = new DivideByZeroException(); - - var policy = Policy - .Handle() - .WaitAndRetry(5, - sleepDurationProvider: (_, ex, _) => - { - capturedExceptionInstance = ex; - return TimeSpan.FromMilliseconds(0); - }, - onRetry: (_, _, _, _) => - { - } - ); - - policy.RaiseException(exceptionInstance); - - capturedExceptionInstance.Should().Be(exceptionInstance); - } + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, - }; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_attempts_with_context_when_using_provider_overload() + { + Action nullOnRetry = null; - var actualRetryWaits = new List(); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); - var policy = Policy - .Handle() - .WaitAndRetry(2, - (_, exc, _) => expectedRetryWaits[exc], - (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) - ); + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) + [Fact] + public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() + { + var expectedRetryWaits = new[] { - policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); - } + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() + }; - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } + var actualRetryWaits = new List(); - [Fact] - public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() - { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; + var policy = Policy + .Handle() + .WaitAndRetry(5, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); + + policy.RaiseException(5); - TimeSpan defaultRetryAfter = 30.Seconds(); + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - var policy = Policy - .Handle() - .WaitAndRetry(1, - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + [Fact] + public void Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() + { + object capturedExceptionInstance = null; - bool failedOnce = false; - policy.Execute(context => - { - // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. - context["RetryAfter"] = expectedRetryDuration; + DivideByZeroException exceptionInstance = new DivideByZeroException(); - if (!failedOnce) + var policy = Policy + .Handle() + .WaitAndRetry(5, + sleepDurationProvider: (_, ex, _) => + { + capturedExceptionInstance = ex; + return TimeSpan.FromMilliseconds(0); + }, + onRetry: (_, _, _, _) => { - failedOnce = true; - throw new DivideByZeroException(); } - }, - new { RetryAfter = defaultRetryAfter }.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. - ); + ); - actualRetryDuration.Should().Be(expectedRetryDuration); - } - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - bool retryInvoked = false; + policy.RaiseException(exceptionInstance); - Action onRetry = (_, _) => { retryInvoked = true; }; + capturedExceptionInstance.Should().Be(exceptionInstance); + } - var policy = Policy - .Handle() - .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; - retryInvoked.Should().BeFalse(); - } + var actualRetryWaits = new List(); - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_context() + var policy = Policy + .Handle() + .WaitAndRetry(2, + (_, exc, _) => expectedRetryWaits[exc], + (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) + ); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) { - bool retryInvoked = false; + policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); + } - Action onRetry = (_, _, _) => { retryInvoked = true; }; + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } - Policy policy = Policy - .Handle() - .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); + [Fact] + public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + TimeSpan defaultRetryAfter = 30.Seconds(); - retryInvoked.Should().BeFalse(); - } + var policy = Policy + .Handle() + .WaitAndRetry(1, + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_attempts_with_context() + bool failedOnce = false; + policy.Execute(context => { - bool retryInvoked = false; + // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. + context["RetryAfter"] = expectedRetryDuration; - Action onRetry = (_, _, _, _) => { retryInvoked = true; }; + if (!failedOnce) + { + failedOnce = true; + throw new DivideByZeroException(); + } + }, + new { RetryAfter = defaultRetryAfter }.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. + ); - Policy policy = Policy - .Handle() - .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); + actualRetryDuration.Should().Be(expectedRetryDuration); + } + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_without_context() + { + bool retryInvoked = false; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + Action onRetry = (_, _) => { retryInvoked = true; }; - retryInvoked.Should().BeFalse(); - } + var policy = Policy + .Handle() + .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); - #region Sync cancellation tests + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + retryInvoked.Should().BeFalse(); + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_context() + { + bool retryInvoked = false; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + Action onRetry = (_, _, _) => { retryInvoked = true; }; - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + Policy policy = Policy + .Handle() + .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); - cancellationTokenSource.Cancel(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + retryInvoked.Should().BeFalse(); + } - attemptsInvoked.Should().Be(0); - } + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_attempts_with_context() + { + bool retryInvoked = false; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + Action onRetry = (_, _, _, _) => { retryInvoked = true; }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + Policy policy = Policy + .Handle() + .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + retryInvoked.Should().BeFalse(); + } - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + #region Sync cancellation tests - attemptsInvoked.Should().Be(1); - } + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + attemptsInvoked.Should().Be(0); + } - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - attemptsInvoked.Should().Be(1); - } + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - SystemClock.Sleep = (timeSpan, ct) => Task.Delay(timeSpan, ct).Wait(ct); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - TimeSpan shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; + attemptsInvoked.Should().Be(1 + 3); + } - var policy = Policy - .Handle() - .WaitAndRetry(new[] { retryDelay }); + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - Stopwatch watch = new Stopwatch(); - watch.Start(); + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 1, - AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. - ActionObservesCancellation = false - }; + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - cancellationTokenSource.CancelAfter(shimTimeSpan); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - watch.Stop(); + attemptsInvoked.Should().Be(1 + 3); + } - attemptsInvoked.Should().Be(1); + [Fact] + public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - watch.Elapsed.Should().BeLessThan(retryDelay); - watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: TimeSpan.FromMilliseconds((int)shimTimeSpan.TotalMilliseconds / 2)); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. - } + SystemClock.Sleep = (timeSpan, ct) => Task.Delay(timeSpan, ct).Wait(ct); - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, - (_, _) => - { - cancellationTokenSource.Cancel(); - }); + var policy = Policy + .Handle() + .WaitAndRetry(new[] { retryDelay }); - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + Stopwatch watch = new Stopwatch(); + watch.Start(); + + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 1, + AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. + ActionObservesCancellation = false + }; + + cancellationTokenSource.CancelAfter(shimTimeSpan); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) .Should().Throw() .And.CancellationToken.Should().Be(cancellationToken); + watch.Stop(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + watch.Elapsed.Should().BeLessThan(retryDelay); + watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: TimeSpan.FromMilliseconds((int)shimTimeSpan.TotalMilliseconds / 2)); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. + } - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, + (_, _) => + { + cancellationTokenSource.Cancel(); + }); - bool? result = null; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null - }; + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - result.Should().BeTrue(); + attemptsInvoked.Should().Be(1); + } - attemptsInvoked.Should().Be(1); - } + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - int attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + bool? result = null; - bool? result = null; + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null + }; - PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + result.Should().BeTrue(); - result.Should().Be(null); + attemptsInvoked.Should().Be(1); + } - attemptsInvoked.Should().Be(1); - } + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + int attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - #endregion + bool? result = null; - public void Dispose() + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - SystemClock.Reset(); - } + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + + result.Should().Be(null); + attemptsInvoked.Should().Be(1); } -} \ No newline at end of file + + #endregion + + public void Dispose() + { + SystemClock.Reset(); + } + +} diff --git a/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs index 44f90ab8d9d..f14e70a44fc 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs @@ -7,56 +7,55 @@ using Xunit; using FluentAssertions.Extensions; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetryTResultAsyncSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetryTResultAsyncSpecs : IDisposable + public WaitAndRetryTResultAsyncSpecs() { - public WaitAndRetryTResultAsyncSpecs() - { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ - - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetryAsync(2, - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _, _) => - { - actualRetryWaits.Add(timeSpan); - return TaskHelper.EmptyTask; - }); - - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(async () => + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryAsync(2, + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _, _) => { - await TaskHelper.EmptyTask; - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; }); - } - - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } - public void Dispose() + using (var enumerator = expectedRetryWaits.GetEnumerator()) { - SystemClock.Reset(); + await policy.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); } + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } -} \ No newline at end of file + + public void Dispose() + { + SystemClock.Reset(); + } + +} diff --git a/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs index f5346a462f8..cb7dac2f693 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs @@ -6,52 +6,51 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry +namespace Polly.Specs.Retry; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class WaitAndRetryTResultSpecs : IDisposable { - [Collection(Constants.SystemClockDependentTestCollection)] - public class WaitAndRetryTResultSpecs : IDisposable + public WaitAndRetryTResultSpecs() { - public WaitAndRetryTResultSpecs() - { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - Dictionary expectedRetryWaits = new Dictionary(){ + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetry(2, - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) - ); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetry(2, + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) + ); - using (var enumerator = expectedRetryWaits.GetEnumerator()) + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => { - policy.Execute(() => - { - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; - }); - } - - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); } - public void Dispose() - { - SystemClock.Reset(); - } + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + public void Dispose() + { + SystemClock.Reset(); } -} \ No newline at end of file + +} diff --git a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs index 949a4196a5b..1525cc092e3 100644 --- a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs @@ -8,802 +8,801 @@ using System.Threading.Tasks; using Xunit; -namespace Polly.Specs.Timeout +namespace Polly.Specs.Timeout; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class TimeoutAsyncSpecs : TimeoutSpecsBase { - [Collection(Constants.SystemClockDependentTestCollection)] - public class TimeoutAsyncSpecs : TimeoutSpecsBase + #region Configuration + + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() { - #region Configuration + Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(0); - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(0); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(-10); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(-10); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(3); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(3); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(int.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(int.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.TimeoutAsync((Func)null); - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.TimeoutAsync((Func)null); + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + #endregion - #endregion + #region Timeout operation - pessimistic - #region Timeout operation - pessimistic + [Fact] + public async Task Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + { + TimeSpan timeout = TimeSpan.FromMilliseconds(50); - [Fact] - public async Task Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + + await policy.Awaiting(p => p.ExecuteAsync(async () => { - TimeSpan timeout = TimeSpan.FromMilliseconds(50); + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + })).Should().ThrowAsync(); + } - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - })).Should().ThrowAsync(); - } + ResultPrimitive result = ResultPrimitive.Undefined; - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + Func act = async () => { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + }; - ResultPrimitive result = ResultPrimitive.Undefined; + act.Should().NotThrowAsync(); + result.Should().Be(ResultPrimitive.Good); + } - Func act = async () => - { - result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - }; + [Fact] + public async Task Should_throw_timeout_after_correct_duration__pessimistic() + { + Stopwatch watch = new Stopwatch(); - act.Should().NotThrowAsync(); - result.Should().Be(ResultPrimitive.Good); - } + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public async Task Should_throw_timeout_after_correct_duration__pessimistic() - { - Stopwatch watch = new Stopwatch(); + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + watch.Start(); + await policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + })) + .Should().ThrowAsync(); + watch.Stop(); - watch.Start(); - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - })) - .Should().ThrowAsync(); - watch.Stop(); + [Fact] + public async Task Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); + } - [Fact] - public async Task Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + #endregion - await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); - } - #endregion + #region Timeout operation - optimistic + [Fact] + public async Task Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + { + TimeSpan timeout = TimeSpan.FromMilliseconds(50); - #region Timeout operation - optimistic + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - [Fact] - public async Task Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + await policy.Awaiting(p => p.ExecuteAsync(async ct => { - TimeSpan timeout = TimeSpan.FromMilliseconds(50); + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + + }, userCancellationToken)) + .Should().ThrowAsync(); + } - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + Func act = async () => { + result = await policy.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; - }, userCancellationToken)) - .Should().ThrowAsync(); - } + act.Should().NotThrowAsync(); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + [Fact] + public async Task Should_throw_timeout_after_correct_duration__optimistic() + { + Stopwatch watch = new Stopwatch(); - Func act = async () => { - result = await policy.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - act.Should().NotThrowAsync(); - result.Should().Be(ResultPrimitive.Good); - } + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - [Fact] - public async Task Should_throw_timeout_after_correct_duration__optimistic() + watch.Start(); + await policy.Awaiting(p => p.ExecuteAsync(async ct => { - Stopwatch watch = new Stopwatch(); + await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); + }, userCancellationToken)) + .Should().ThrowAsync(); + watch.Stop(); - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + [Fact] + public async Task Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - watch.Start(); - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); - }, userCancellationToken)) - .Should().ThrowAsync(); - watch.Stop(); + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); + } - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + #endregion - [Fact] - public async Task Should_rethrow_exception_from_inside_delegate__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); + [Fact] + public async Task Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + { + int timeout = 5; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + { + await policy.Awaiting(p => p.ExecuteAsync(async + _ => { + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + }, userTokenSource.Token) + ).Should().ThrowAsync(); // ... it's still the timeout we expect. } + } - #endregion + [Fact] + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + bool executed = false; - [Fact] - public async Task Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - int timeout = 5; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + cts.Cancel(); - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + await policy.Awaiting(p => p.ExecuteAsync(_ => { - await policy.Awaiting(p => p.ExecuteAsync(async - _ => { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - }, userTokenSource.Token) - ).Should().ThrowAsync(); // ... it's still the timeout we expect. - } + executed = true; + return TaskHelper.EmptyTask; + }, cts.Token)) + .Should().ThrowAsync(); } - [Fact] - public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); + executed.Should().BeFalse(); + } - bool executed = false; + #endregion - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - await policy.Awaiting(p => p.ExecuteAsync(_ => - { - executed = true; - return TaskHelper.EmptyTask; - }, cts.Token)) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + { + int timeout = 10; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - executed.Should().BeFalse(); + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + { + await policy.Awaiting(p => p.ExecuteAsync( + ct => { + userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution + return TaskHelper.EmptyTask; + }, userTokenSource.Token) // ... with user token. + ).Should().ThrowAsync(); } + } - #endregion + [Fact] + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) + bool executed = false; - [Fact] - public async Task Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - int timeout = 10; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + cts.Cancel(); - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + await policy.Awaiting(p => p.ExecuteAsync(_ => { - await policy.Awaiting(p => p.ExecuteAsync( - ct => { - userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution - return TaskHelper.EmptyTask; - }, userTokenSource.Token) // ... with user token. - ).Should().ThrowAsync(); - } + executed = true; + return TaskHelper.EmptyTask; + }, cts.Token)) + .Should().ThrowAsync(); } - [Fact] - public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); + executed.Should().BeFalse(); + } - bool executed = false; + [Fact] + public async Task Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() + { + var userException = new Exception(); + var shimTimeSpan = TimeSpan.FromSeconds(0.2); + var policy = Policy.TimeoutAsync(shimTimeSpan, TimeoutStrategy.Optimistic); - using (CancellationTokenSource cts = new CancellationTokenSource()) + var thrown = await policy.Awaiting(p => p.ExecuteAsync(async _ => { - cts.Cancel(); - - await policy.Awaiting(p => p.ExecuteAsync(_ => + try { - executed = true; - return TaskHelper.EmptyTask; - }, cts.Token)) - .Should().ThrowAsync(); - } + await SystemClock.SleepAsync(shimTimeSpan + shimTimeSpan, CancellationToken.None); + } + catch + { + // Throw a different exception - this exception should not be masked. + // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown + // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. + // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. + throw userException; + } + + throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); + + }, CancellationToken.None)) + .Should() + .ThrowAsync(); + + thrown.NotBeOfType(); + thrown.NotBeOfType(); + thrown.NotBeOfType(); + thrown.Which.Should().BeSameAs(userException); + } - executed.Should().BeFalse(); - } + #endregion - [Fact] - public async Task Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() - { - var userException = new Exception(); - var shimTimeSpan = TimeSpan.FromSeconds(0.2); - var policy = Policy.TimeoutAsync(shimTimeSpan, TimeoutStrategy.Optimistic); + #region onTimeout overload - pessimistic - var thrown = await policy.Awaiting(p => p.ExecuteAsync(async _ => - { - try - { - await SystemClock.SleepAsync(shimTimeSpan + shimTimeSpan, CancellationToken.None); - } - catch - { - // Throw a different exception - this exception should not be masked. - // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown - // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. - // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. - throw userException; - } - - throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); - - }, CancellationToken.None)) - .Should() - .ThrowAsync(); - - thrown.NotBeOfType(); - thrown.NotBeOfType(); - thrown.NotBeOfType(); - thrown.Which.Should().BeSameAs(userException); - } + [Fact] + public async Task Should_call_ontimeout_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - #endregion + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - #region onTimeout overload - pessimistic + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - [Fact] - public async Task Should_call_ontimeout_with_configured_timeout__pessimistic() + await policy.Awaiting(p => p.ExecuteAsync(async () => { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + })) + .Should().ThrowAsync(); - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + [Fact] + public async Task Should_call_ontimeout_with_passed_context__pessimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => + { + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - await policy.Awaiting(p => p.ExecuteAsync(async () => + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + await policy.Awaiting(p => p.ExecuteAsync(async _ => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - })) + }, contextPassedToExecute)) .Should().ThrowAsync(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - [Fact] - public async Task Should_call_ontimeout_with_passed_context__pessimistic() + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + await policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + })) + .Should().ThrowAsync(); - await policy.Awaiting(p => p.ExecuteAsync(async _ => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - }, contextPassedToExecute)) - .Should().ThrowAsync(); + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - })) - .Should().ThrowAsync(); + await policy.Awaiting(p => p.ExecuteAsync(async _ => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + }, context)) + .Should().ThrowAsync(); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + [Fact] + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); + await policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + })) + .Should().ThrowAsync(); - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + taskPassedToOnTimeout.Should().NotBeNull(); + } - await policy.Awaiting(p => p.ExecuteAsync(async _ => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - }, context)) - .Should().ThrowAsync(); + [Fact] + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. + // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + Exception exceptionToThrow = new DivideByZeroException(); - [Fact] - public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. + return TaskHelper.EmptyTask; + }; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - })) - .Should().ThrowAsync(); + await policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); + throw exceptionToThrow; + })) + .Should().ThrowAsync(); - taskPassedToOnTimeout.Should().NotBeNull(); - } + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - [Fact] - public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. - // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + } - Exception exceptionToThrow = new DivideByZeroException(); + [Fact] + public async Task Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. - return TaskHelper.EmptyTask; - }; + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - await policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); - throw exceptionToThrow; + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); })) .Should().ThrowAsync(); - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - } + #endregion - [Fact] - public async Task Should_call_ontimeout_with_timing_out_exception__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + #region onTimeout overload - optimistic - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => - { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; + [Fact] + public async Task Should_call_ontimeout_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - })) - .Should().ThrowAsync(); + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + await policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + }, userCancellationToken)) + .Should().ThrowAsync(); - #endregion + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - #region onTimeout overload - optimistic + [Fact] + public async Task Should_call_ontimeout_with_passed_context__optimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - [Fact] - public async Task Should_call_ontimeout_with_configured_timeout__optimistic() + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - await policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) + }, contextPassedToExecute, userCancellationToken)) .Should().ThrowAsync(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - [Fact] - public async Task Should_call_ontimeout_with_passed_context__optimistic() + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + await policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + }, userCancellationToken)) + .Should().ThrowAsync(); - await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, contextPassedToExecute, userCancellationToken)) - .Should().ThrowAsync(); + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) - .Should().ThrowAsync(); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; + }, context, userCancellationToken)) + .Should().ThrowAsync(); - await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - }, context, userCancellationToken)) - .Should().ThrowAsync(); + [Fact] + public async Task Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - [Fact] - public async Task Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + await policy.Awaiting(p => p.ExecuteAsync(async ct => { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + }, userCancellationToken)) + .Should().ThrowAsync(); - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) - .Should().ThrowAsync(); + taskPassedToOnTimeout.Should().BeNull(); + } - taskPassedToOnTimeout.Should().BeNull(); - } + [Fact] + public async Task Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - [Fact] - public async Task Should_call_ontimeout_with_timing_out_exception__optimistic() + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => - { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - }, userCancellationToken)) - .Should().ThrowAsync(); + await policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + }, userCancellationToken)) + .Should().ThrowAsync(); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Timeout/TimeoutSpecs.cs b/src/Polly.Specs/Timeout/TimeoutSpecs.cs index ba832b25c96..9e87f005cec 100644 --- a/src/Polly.Specs/Timeout/TimeoutSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutSpecs.cs @@ -8,792 +8,791 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Timeout +namespace Polly.Specs.Timeout; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class TimeoutSpecs : TimeoutSpecsBase { - [Collection(Constants.SystemClockDependentTestCollection)] - public class TimeoutSpecs : TimeoutSpecsBase + #region Configuration + + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() { - #region Configuration + Action policy = () => Policy.Timeout(TimeSpan.Zero); - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() - { - Action policy = () => Policy.Timeout(TimeSpan.Zero); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.Timeout(0); - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.Timeout(0); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(-10); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(-10); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(3); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(3); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.Timeout(TimeSpan.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.Timeout(TimeSpan.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.Timeout(int.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.Timeout(int.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout_overload() + { + Action doNothing = (_, _, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout_overload() - { - Action doNothing = (_, _, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout_overload() + { + Action doNothing = (_, _, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout_overload() - { - Action doNothing = (_, _, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_timespan() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_overload_is_null_with_timespan() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_seconds() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_overload_is_null_with_seconds() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.Timeout((Func)null); - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.Timeout((Func)null); + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_timeoutprovider() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_overload_is_null_with_timeoutprovider() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + #endregion - #endregion + #region Timeout operation - pessimistic - #region Timeout operation - pessimistic + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Pessimistic); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); + } - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + Action act = () => { + result = policy.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; - Action act = () => { - result = policy.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + [Fact] + public void Should_throw_timeout_after_correct_duration__pessimistic() + { + Stopwatch watch = new Stopwatch(); - [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() - { - Stopwatch watch = new Stopwatch(); + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + watch.Start(); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None))) + .Should().Throw(); + watch.Stop(); - watch.Start(); - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None))) - .Should().Throw(); - watch.Stop(); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + } - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); - } + [Fact] + public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; - [Fact] - public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; + // Check to see if nested aggregate exceptions are unwrapped correctly + AggregateException exception = new AggregateException(msg, new NotImplementedException()); - // Check to see if nested aggregate exceptions are unwrapped correctly - AggregateException exception = new AggregateException(msg, new NotImplementedException()); + policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); })) + .Should().Throw() + .WithMessage(exception.Message) + .Where(e => e.InnerException is NotImplementedException) + .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); + } - policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); })) - .Should().Throw() - .WithMessage(exception.Message) - .Where(e => e.InnerException is NotImplementedException) - .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); - } + [Fact] + public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; + + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + AggregateException aggregateException = new AggregateException(msg, innerException1, innerException2); + Action action = () => throw aggregateException; + + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + action.Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + + policy.Invoking(p => p.Execute(action)).Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - [Fact] - public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; - - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - AggregateException aggregateException = new AggregateException(msg, innerException1, innerException2); - Action action = () => throw aggregateException; - - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - action.Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - - policy.Invoking(p => p.Execute(action)).Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + [Fact] + public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + Action action = () => { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + Task task1 = Task.Run(() => throw innerException1); + Task task2 = Task.Run(() => throw innerException2); + Task.WhenAll(task1, task2).Wait(); + }; - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - Action action = () => - { - Task task1 = Task.Run(() => throw innerException1); - Task task2 = Task.Run(() => throw innerException2); - Task.WhenAll(task1, task2).Wait(); - }; + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + action.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - action.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + policy.Invoking(p => p.Execute(action)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - policy.Invoking(p => p.Execute(action)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + [Fact] + public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + Action action = () => { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + Action action1 = () => throw innerException1; + Action action2 = () => throw innerException2; + Parallel.Invoke(action1, action2); + }; - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - Action action = () => - { - Action action1 = () => throw innerException1; - Action action2 = () => throw innerException2; - Parallel.Invoke(action1, action2); - }; + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + action.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - action.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + policy.Invoking(p => p.Execute(action)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - policy.Invoking(p => p.Execute(action)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + #endregion - #endregion + #region Timeout operation - optimistic - #region Timeout operation - optimistic + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. + .Should().Throw(); + } - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. - .Should().Throw(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; - - Action act = () => { - result = policy.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; - - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + Action act = () => { + result = policy.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; - [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() - { - Stopwatch watch = new Stopwatch(); + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout); - var userCancellationToken = CancellationToken.None; + [Fact] + public void Should_throw_timeout_after_correct_duration__optimistic() + { + Stopwatch watch = new Stopwatch(); - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout); + var userCancellationToken = CancellationToken.None; - watch.Start(); - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(10), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. - .Should().Throw(); - watch.Stop(); + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + watch.Start(); + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(10), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. + .Should().Throw(); + watch.Stop(); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + [Fact] + public void Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - } + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); - #endregion + } - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + #endregion - [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() - { - int timeout = 5; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) - { - policy.Invoking(p => p.Execute( - _ => - { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - } - , userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. - } - } + [Fact] + public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + { + int timeout = 5; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { - var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); - - bool executed = false; - - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); - - policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) - .Should().Throw(); - } - - executed.Should().BeFalse(); + policy.Invoking(p => p.Execute( + _ => + { + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + } + , userTokenSource.Token) + ).Should().Throw(); // ... it's still the timeout we expect. } + } - #endregion + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + { + var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) + bool executed = false; - [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - int timeout = 10; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); + cts.Cancel(); - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) - { - policy.Invoking(p => p.Execute( - ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); } // Simulate cancel in the middle of execution - , userTokenSource.Token) // ... with user token. - ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. - } + policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) + .Should().Throw(); } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() - { - var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); + executed.Should().BeFalse(); + } - bool executed = false; + #endregion - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) - .Should().Throw(); - } + [Fact] + public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + { + int timeout = 10; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); - executed.Should().BeFalse(); + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + { + policy.Invoking(p => p.Execute( + ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); } // Simulate cancel in the middle of execution + , userTokenSource.Token) // ... with user token. + ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. } + } + + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); + + bool executed = false; - [Fact] - public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - var userException = new Exception(); - var shimTimeSpan = TimeSpan.FromSeconds(0.2); - var policy = Policy.Timeout(shimTimeSpan, TimeoutStrategy.Optimistic); + cts.Cancel(); - var thrown = policy.Invoking(p => p.Execute(_ => - { - try - { - SystemClock.Sleep(shimTimeSpan + shimTimeSpan, CancellationToken.None); - } - catch - { - // Throw a different exception - this exception should not be masked. - // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown - // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. - // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. - throw userException; - } - - throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); - - }, CancellationToken.None)) - .Should() - .Throw() - .Which; - - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().BeSameAs(userException); + policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) + .Should().Throw(); } - #endregion + executed.Should().BeFalse(); + } - #region onTimeout overload - pessimistic + [Fact] + public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() + { + var userException = new Exception(); + var shimTimeSpan = TimeSpan.FromSeconds(0.2); + var policy = Policy.Timeout(shimTimeSpan, TimeoutStrategy.Optimistic); - [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + var thrown = policy.Invoking(p => p.Execute(_ => + { + try + { + SystemClock.Sleep(shimTimeSpan + shimTimeSpan, CancellationToken.None); + } + catch + { + // Throw a different exception - this exception should not be masked. + // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown + // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. + // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. + throw userException; + } + + throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); + + }, CancellationToken.None)) + .Should() + .Throw() + .Which; + + thrown.Should().NotBeOfType(); + thrown.Should().NotBeOfType(); + thrown.Should().NotBeOfType(); + thrown.Should().BeSameAs(userException); + } - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + #endregion - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + #region onTimeout overload - pessimistic - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), contextPassedToExecute)) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_passed_context__pessimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), contextPassedToExecute)) + .Should().Throw(); - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") {["timeout"] = TimeSpan.FromMilliseconds(25* programaticallyControlledDelay) }; + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), context)) - .Should().Throw(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") {["timeout"] = TimeSpan.FromMilliseconds(25* programaticallyControlledDelay) }; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), context)) + .Should().Throw(); - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - taskPassedToOnTimeout.Should().NotBeNull(); - } + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. - // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - Exception exceptionToThrow = new DivideByZeroException(); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); - }; + taskPassedToOnTimeout.Should().NotBeNull(); + } - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. + // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - throw exceptionToThrow; - })) - .Should().Throw(); + Exception exceptionToThrow = new DivideByZeroException(); - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); + }; - } + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + policy.Invoking(p => p.Execute(() => { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + throw exceptionToThrow; + })) + .Should().Throw(); - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + } - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - #endregion + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - #region onTimeout overload - optimistic + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + #endregion - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + #region onTimeout overload - optimistic - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) + .Should().Throw(); - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), contextPassedToExecute, userCancellationToken)) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_passed_context__optimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), contextPassedToExecute, userCancellationToken)) + .Should().Throw(); - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) - .Should().Throw(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) + .Should().Throw(); - var userCancellationToken = CancellationToken.None; + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) - .Should().Throw(); + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + var userCancellationToken = CancellationToken.None; - [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) + .Should().Throw(); - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) - .Should().Throw(); + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - taskPassedToOnTimeout.Should().BeNull(); - } + [Fact] + public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) + .Should().Throw(); - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + taskPassedToOnTimeout.Should().BeNull(); + } - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - #endregion + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) + .Should().Throw(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); } + + #endregion } diff --git a/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs b/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs index d86d7b7c7a3..e5deefbea6e 100644 --- a/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs +++ b/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs @@ -4,100 +4,99 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Timeout +namespace Polly.Specs.Timeout; + +/// +/// Provides common functionality for timeout specs, which abstracts out both SystemClock.Sleep, and SystemClock.CancelTokenAfter. +/// Polly's TimeoutPolicy uses timing-out CancellationTokens to drive timeouts. +/// For tests, rather than letting .NET's timers drive the timing out of CancellationTokens, we override SystemClock.CancelTokenAfter and SystemClock.Sleep to make the tests run fast. +/// +/// +public abstract class TimeoutSpecsBase : IDisposable { - /// - /// Provides common functionality for timeout specs, which abstracts out both SystemClock.Sleep, and SystemClock.CancelTokenAfter. - /// Polly's TimeoutPolicy uses timing-out CancellationTokens to drive timeouts. - /// For tests, rather than letting .NET's timers drive the timing out of CancellationTokens, we override SystemClock.CancelTokenAfter and SystemClock.Sleep to make the tests run fast. - /// - /// - public abstract class TimeoutSpecsBase : IDisposable - { - // xUnit creates a new class instance per test, so these variables are isolated per test. + // xUnit creates a new class instance per test, so these variables are isolated per test. - // Track a CancellationTokenSource, and when it might be cancelled at. - private CancellationTokenSource _trackedTokenSource; - private DateTimeOffset _cancelAt = DateTimeOffset.MaxValue; + // Track a CancellationTokenSource, and when it might be cancelled at. + private CancellationTokenSource _trackedTokenSource; + private DateTimeOffset _cancelAt = DateTimeOffset.MaxValue; - private DateTimeOffset _offsetUtcNow = DateTimeOffset.UtcNow; - private DateTime _utcNow = DateTime.UtcNow; - - protected TimeoutSpecsBase() + private DateTimeOffset _offsetUtcNow = DateTimeOffset.UtcNow; + private DateTime _utcNow = DateTime.UtcNow; + + protected TimeoutSpecsBase() + { + // Override the SystemClock, to return time stored in variables we manipulate. + SystemClock.DateTimeOffsetUtcNow = () => _offsetUtcNow; + SystemClock.UtcNow = () => _utcNow; + + // Override SystemClock.CancelTokenAfter to record when the policy wants the token to cancel. + SystemClock.CancelTokenAfter = (tokenSource, timespan) => { - // Override the SystemClock, to return time stored in variables we manipulate. - SystemClock.DateTimeOffsetUtcNow = () => _offsetUtcNow; - SystemClock.UtcNow = () => _utcNow; + if (_trackedTokenSource != null && tokenSource != _trackedTokenSource) throw new InvalidOperationException("Timeout tests cannot track more than one timing out token at a time."); - // Override SystemClock.CancelTokenAfter to record when the policy wants the token to cancel. - SystemClock.CancelTokenAfter = (tokenSource, timespan) => - { - if (_trackedTokenSource != null && tokenSource != _trackedTokenSource) throw new InvalidOperationException("Timeout tests cannot track more than one timing out token at a time."); + _trackedTokenSource = tokenSource; - _trackedTokenSource = tokenSource; + DateTimeOffset newCancelAt = _offsetUtcNow.Add(timespan); + _cancelAt = newCancelAt < _cancelAt ? newCancelAt : _cancelAt; - DateTimeOffset newCancelAt = _offsetUtcNow.Add(timespan); - _cancelAt = newCancelAt < _cancelAt ? newCancelAt : _cancelAt; + SystemClock.Sleep(TimeSpan.Zero, CancellationToken.None); // Invoke our custom definition of sleep, to check for immediate cancellation. + }; - SystemClock.Sleep(TimeSpan.Zero, CancellationToken.None); // Invoke our custom definition of sleep, to check for immediate cancellation. - }; + // Override SystemClock.Sleep, to manipulate our artificial clock. And - if it means sleeping beyond the time when a tracked token should cancel - cancel it! + SystemClock.Sleep = (sleepTimespan, sleepCancellationToken) => + { + if (sleepCancellationToken.IsCancellationRequested) return; - // Override SystemClock.Sleep, to manipulate our artificial clock. And - if it means sleeping beyond the time when a tracked token should cancel - cancel it! - SystemClock.Sleep = (sleepTimespan, sleepCancellationToken) => + if (_trackedTokenSource == null || _trackedTokenSource.IsCancellationRequested) { - if (sleepCancellationToken.IsCancellationRequested) return; - - if (_trackedTokenSource == null || _trackedTokenSource.IsCancellationRequested) + // Not tracking any CancellationToken (or already cancelled) - just advance time. + _utcNow += sleepTimespan; + _offsetUtcNow += sleepTimespan; + } + else + { + // Tracking something to cancel - does this sleep hit time to cancel? + TimeSpan timeToCancellation = _cancelAt - _offsetUtcNow; + if (sleepTimespan >= timeToCancellation) { - // Not tracking any CancellationToken (or already cancelled) - just advance time. - _utcNow += sleepTimespan; - _offsetUtcNow += sleepTimespan; + // Cancel! (And advance time only to the instant of cancellation) + _offsetUtcNow += timeToCancellation; + _utcNow += timeToCancellation; + + // (and stop tracking it after cancelling; it can't be cancelled twice, so there is no need, and the owner may dispose it) + CancellationTokenSource copySource = _trackedTokenSource; + _trackedTokenSource = null; + copySource.Cancel(); + copySource.Token.ThrowIfCancellationRequested(); } else { - // Tracking something to cancel - does this sleep hit time to cancel? - TimeSpan timeToCancellation = _cancelAt - _offsetUtcNow; - if (sleepTimespan >= timeToCancellation) - { - // Cancel! (And advance time only to the instant of cancellation) - _offsetUtcNow += timeToCancellation; - _utcNow += timeToCancellation; - - // (and stop tracking it after cancelling; it can't be cancelled twice, so there is no need, and the owner may dispose it) - CancellationTokenSource copySource = _trackedTokenSource; - _trackedTokenSource = null; - copySource.Cancel(); - copySource.Token.ThrowIfCancellationRequested(); - } - else - { - // (not yet time to cancel - just advance time) - _utcNow += sleepTimespan; - _offsetUtcNow += sleepTimespan; - } + // (not yet time to cancel - just advance time) + _utcNow += sleepTimespan; + _offsetUtcNow += sleepTimespan; } - }; + } + }; - SystemClock.SleepAsync = (sleepTimespan, cancellationToken) => - { - SystemClock.Sleep(sleepTimespan, cancellationToken); - return Task.FromResult(true); - }; - } - - public void Dispose() + SystemClock.SleepAsync = (sleepTimespan, cancellationToken) => { - SystemClock.Reset(); - } + SystemClock.Sleep(sleepTimespan, cancellationToken); + return Task.FromResult(true); + }; + } - /// - /// A helper method which simply throws the passed exception. Supports tests verifying the stack trace of where an exception was thrown, by throwing that exception from a specific (other) location. - /// - /// The exception to throw. - [MethodImpl(MethodImplOptions.NoInlining)] // Tests that use this method assert that the exception was thrown from within this method; therefore, it is essential that - protected void Helper_ThrowException(Exception ex) - { - throw ex; - } + public void Dispose() + { + SystemClock.Reset(); + } + + /// + /// A helper method which simply throws the passed exception. Supports tests verifying the stack trace of where an exception was thrown, by throwing that exception from a specific (other) location. + /// + /// The exception to throw. + [MethodImpl(MethodImplOptions.NoInlining)] // Tests that use this method assert that the exception was thrown from within this method; therefore, it is essential that + protected void Helper_ThrowException(Exception ex) + { + throw ex; } } diff --git a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs index de3b85e60b4..097fef223bd 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -8,772 +8,771 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Timeout +namespace Polly.Specs.Timeout; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class TimeoutTResultAsyncSpecs : TimeoutSpecsBase { - [Collection(Constants.SystemClockDependentTestCollection)] - public class TimeoutTResultAsyncSpecs : TimeoutSpecsBase + #region Configuration + + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() { - #region Configuration + Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(0); - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(0); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(-10); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(-10); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(3); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(3); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(int.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(int.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.TimeoutAsync((Func)null); - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.TimeoutAsync((Func)null); + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Func onTimeoutAsync = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Func onTimeoutAsync = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_for_full_argument_list_onTimeout() + { + Func onTimeoutAsync = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_for_full_argument_list_onTimeout() - { - Func onTimeoutAsync = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + #endregion + + #region Timeout operation - pessimistic - #endregion + [Fact] + public async Task Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + { + TimeSpan timeout = TimeSpan.FromMilliseconds(50); - #region Timeout operation - pessimistic + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public async Task Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + await policy.Awaiting(p => p.ExecuteAsync(async () => { - TimeSpan timeout = TimeSpan.FromMilliseconds(50); + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })).Should().ThrowAsync(); + } - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })).Should().ThrowAsync(); - } + ResultPrimitive result = ResultPrimitive.Undefined; - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + Func act = async () => result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - ResultPrimitive result = ResultPrimitive.Undefined; + act.Should().NotThrowAsync(); + result.Should().Be(ResultPrimitive.Good); + } - Func act = async () => result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + [Fact] + public async Task Should_throw_timeout_after_correct_duration__pessimistic() + { + Stopwatch watch = new Stopwatch(); - act.Should().NotThrowAsync(); - result.Should().Be(ResultPrimitive.Good); - } + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public async Task Should_throw_timeout_after_correct_duration__pessimistic() + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + + watch.Start(); + await policy.Awaiting(p => p.ExecuteAsync(async () => { - Stopwatch watch = new Stopwatch(); + await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().ThrowAsync(); + watch.Stop(); - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + [Fact] + public async Task Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - watch.Start(); - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().ThrowAsync(); - watch.Stop(); + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); + } - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + #endregion - [Fact] - public async Task Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + #region Timeout operation - optimistic - await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); - } + [Fact] + public async Task Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - #endregion + await policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)).Should().ThrowAsync(); + } - #region Timeout operation - optimistic + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - [Fact] - public async Task Should_throw_when_timeout_is_less_than_execution_duration__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + ResultPrimitive result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)).Should().ThrowAsync(); - } + Func act = async () => result = await policy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), userCancellationToken); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + act.Should().NotThrowAsync(); + result.Should().Be(ResultPrimitive.Good); + } - ResultPrimitive result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + [Fact] + public async Task Should_throw_timeout_after_correct_duration__optimistic() + { + Stopwatch watch = new Stopwatch(); - Func act = async () => result = await policy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), userCancellationToken); + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - act.Should().NotThrowAsync(); - result.Should().Be(ResultPrimitive.Good); - } + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - [Fact] - public async Task Should_throw_timeout_after_correct_duration__optimistic() + watch.Start(); + await policy.Awaiting(p => p.ExecuteAsync(async ct => { - Stopwatch watch = new Stopwatch(); + await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().ThrowAsync(); + watch.Stop(); - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + [Fact] + public async Task Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - watch.Start(); - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().ThrowAsync(); - watch.Stop(); + await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); + } - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + #endregion - [Fact] - public async Task Should_rethrow_exception_from_inside_delegate__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - await policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().ThrowAsync(); + [Fact] + public async Task Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + { + int timeout = 5; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + { + await policy.Awaiting(p => p.ExecuteAsync(async + _ => { + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + return ResultPrimitive.WhateverButTooLate; + }, userTokenSource.Token) + ).Should().ThrowAsync(); // ... it's still the timeout we expect. } + } - #endregion + [Fact] + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + bool executed = false; - [Fact] - public async Task Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - int timeout = 5; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + cts.Cancel(); - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + await policy.Awaiting(p => p.ExecuteAsync(async _ => { - await policy.Awaiting(p => p.ExecuteAsync(async - _ => { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - return ResultPrimitive.WhateverButTooLate; - }, userTokenSource.Token) - ).Should().ThrowAsync(); // ... it's still the timeout we expect. - } + executed = true; + await TaskHelper.EmptyTask; + return ResultPrimitive.WhateverButTooLate; + }, cts.Token)) + .Should().ThrowAsync(); } - [Fact] - public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); + executed.Should().BeFalse(); + } - bool executed = false; + #endregion - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); - - await policy.Awaiting(p => p.ExecuteAsync(async _ => - { - executed = true; - await TaskHelper.EmptyTask; - return ResultPrimitive.WhateverButTooLate; - }, cts.Token)) - .Should().ThrowAsync(); - } + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - executed.Should().BeFalse(); + [Fact] + public async Task Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + { + int timeout = 10; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + { + await policy.Awaiting(p => p.ExecuteAsync( + ct => { + userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution + return Task.FromResult(ResultPrimitive.WhateverButTooLate); + }, userTokenSource.Token) // ... with user token. + ).Should().ThrowAsync(); } + } - #endregion + [Fact] + public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) + bool executed = false; - [Fact] - public async Task Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - int timeout = 10; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + cts.Cancel(); + + await policy.Awaiting(p => p.ExecuteAsync(async _ => { - await policy.Awaiting(p => p.ExecuteAsync( - ct => { - userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution - return Task.FromResult(ResultPrimitive.WhateverButTooLate); - }, userTokenSource.Token) // ... with user token. - ).Should().ThrowAsync(); - } + executed = true; + await TaskHelper.EmptyTask; + return ResultPrimitive.WhateverButTooLate; + }, cts.Token)) + .Should().ThrowAsync(); } - [Fact] - public async Task Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); + executed.Should().BeFalse(); + } - bool executed = false; + #endregion - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + #region onTimeout overload - pessimistic - await policy.Awaiting(p => p.ExecuteAsync(async _ => - { - executed = true; - await TaskHelper.EmptyTask; - return ResultPrimitive.WhateverButTooLate; - }, cts.Token)) - .Should().ThrowAsync(); - } + [Fact] + public async Task Should_call_ontimeout_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - executed.Should().BeFalse(); - } + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - #endregion + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - #region onTimeout overload - pessimistic + await policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().ThrowAsync(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - [Fact] - public async Task Should_call_ontimeout_with_configured_timeout__pessimistic() + [Fact] + public async Task Should_call_ontimeout_with_passed_context__pessimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + await policy.Awaiting(p => p.ExecuteAsync(async _ => { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute)) + .Should().ThrowAsync(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - await policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().ThrowAsync(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - [Fact] - public async Task Should_call_ontimeout_with_passed_context__pessimistic() + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - await policy.Awaiting(p => p.ExecuteAsync(async _ => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute)) - .Should().ThrowAsync(); + await policy.Awaiting(p => p.ExecuteAsync(async _ => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, context)) + .Should().ThrowAsync(); - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + [Fact] + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); + await policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().ThrowAsync(); - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().ThrowAsync(); + taskPassedToOnTimeout.Should().NotBeNull(); + } - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + [Fact] + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. + // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + Exception exceptionToThrow = new DivideByZeroException(); + + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. + return TaskHelper.EmptyTask; + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); + await policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); + throw exceptionToThrow; + })) + .Should().ThrowAsync(); - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - await policy.Awaiting(p => p.ExecuteAsync(async _ => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, context)) - .Should().ThrowAsync(); + } - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + [Fact] + public async Task Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - [Fact] - public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - await policy.Awaiting(p => p.ExecuteAsync(async () => + await policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().ThrowAsync(); - taskPassedToOnTimeout.Should().NotBeNull(); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - [Fact] - public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. - // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + #endregion - Exception exceptionToThrow = new DivideByZeroException(); + #region onTimeout overload - optimistic - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. - return TaskHelper.EmptyTask; - }; + [Fact] + public async Task Should_call_ontimeout_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); - throw exceptionToThrow; - })) - .Should().ThrowAsync(); + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + await policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().ThrowAsync(); - } + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - [Fact] - public async Task Should_call_ontimeout_with_timing_out_exception__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public async Task Should_call_ontimeout_with_passed_context__optimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => - { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => + { + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - await policy.Awaiting(p => p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().ThrowAsync(); + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute, userCancellationToken)) + .Should().ThrowAsync(); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - #endregion + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { - #region onTimeout overload - optimistic + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - [Fact] - public async Task Should_call_ontimeout_with_configured_timeout__optimistic() + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - await policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().ThrowAsync(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public async Task Should_call_ontimeout_with_passed_context__optimistic() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute, userCancellationToken)) - .Should().ThrowAsync(); + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().ThrowAsync(); + await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, context, userCancellationToken)) + .Should().ThrowAsync(); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + [Fact] + public async Task Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; + await policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().ThrowAsync(); - await policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, context, userCancellationToken)) - .Should().ThrowAsync(); + taskPassedToOnTimeout.Should().BeNull(); + } - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + [Fact] + public async Task Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - [Fact] - public async Task Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - await policy.Awaiting(p => p.ExecuteAsync(async ct => + await policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().ThrowAsync(); - taskPassedToOnTimeout.Should().BeNull(); - } - - [Fact] - public async Task Should_call_ontimeout_with_timing_out_exception__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => - { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - await policy.Awaiting(p => p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().ThrowAsync(); - - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs index ec887ff92db..4fa0845b6b5 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs @@ -8,816 +8,815 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Timeout +namespace Polly.Specs.Timeout; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class TimeoutTResultSpecs : TimeoutSpecsBase { - [Collection(Constants.SystemClockDependentTestCollection)] - public class TimeoutTResultSpecs : TimeoutSpecsBase + #region Configuration + + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() { - #region Configuration + Action policy = () => Policy.Timeout(TimeSpan.Zero); - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() - { - Action policy = () => Policy.Timeout(TimeSpan.Zero); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.Timeout(0); - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.Timeout(0); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(-10); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(-10); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(3); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(3); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.Timeout(TimeSpan.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.Timeout(TimeSpan.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.Timeout(int.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.Timeout(int.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan_and_onTimeout_is_full_argument_set() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds_and_onTimeout_is_full_argument_set() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.Timeout((Func)null); - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.Timeout((Func)null); + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_and_onTimeout_is_full_argument_set() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + #endregion - #endregion + #region Timeout operation - pessimistic + + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + { + TimeSpan timeout = TimeSpan.FromMilliseconds(50); - #region Timeout operation - pessimistic + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + policy.Invoking(p => p.Execute(() => { - TimeSpan timeout = TimeSpan.FromMilliseconds(50); + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })).Should().Throw(); + } + + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(() => + Action act = () => { + result = policy.Execute(ct => { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })).Should().Throw(); - } + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + [Fact] + public void Should_throw_timeout_after_correct_duration__pessimistic() + { + Stopwatch watch = new Stopwatch(); - Action act = () => { - result = policy.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() + watch.Start(); + policy.Invoking(p => p.Execute(() => { - Stopwatch watch = new Stopwatch(); + SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().Throw(); + watch.Stop(); - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + [Fact] + public void Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - watch.Start(); - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().Throw(); - watch.Stop(); + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + } - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + [Fact] + public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; - [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + // Check to see if nested aggregate exceptions are unwrapped correctly + AggregateException exception = new AggregateException(msg, new NotImplementedException()); - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); - } + policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); return ResultPrimitive.WhateverButTooLate; })) + .Should().Throw() + .WithMessage(exception.Message) + .Where(e => e.InnerException is NotImplementedException) + .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); + } - [Fact] - public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() + [Fact] + public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; + + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + AggregateException aggregateException = new AggregateException(msg, innerException1, innerException2); + Func func = () => { Helper_ThrowException(aggregateException); return ResultPrimitive.WhateverButTooLate; }; + + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + func.Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + + policy.Invoking(p => p.Execute(func)).Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } + + [Fact] + public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + Func func = () => { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; + Task task1 = Task.Run(() => throw innerException1); + Task task2 = Task.Run(() => throw innerException2); + Task.WhenAll(task1, task2).Wait(); + return ResultPrimitive.WhateverButTooLate; + }; - // Check to see if nested aggregate exceptions are unwrapped correctly - AggregateException exception = new AggregateException(msg, new NotImplementedException()); + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + func.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw() - .WithMessage(exception.Message) - .Where(e => e.InnerException is NotImplementedException) - .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); - } + policy.Invoking(p => p.Execute(func)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - [Fact] - public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; - - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - AggregateException aggregateException = new AggregateException(msg, innerException1, innerException2); - Func func = () => { Helper_ThrowException(aggregateException); return ResultPrimitive.WhateverButTooLate; }; - - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - func.Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - - policy.Invoking(p => p.Execute(func)).Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + [Fact] + public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + Func func = () => { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + Action action1 = () => { throw innerException1; }; + Action action2 = () => throw innerException2; + Parallel.Invoke(action1, action2); + return ResultPrimitive.WhateverButTooLate; + }; - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - Func func = () => - { - Task task1 = Task.Run(() => throw innerException1); - Task task2 = Task.Run(() => throw innerException2); - Task.WhenAll(task1, task2).Wait(); - return ResultPrimitive.WhateverButTooLate; - }; + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + func.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - func.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + policy.Invoking(p => p.Execute(func)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - policy.Invoking(p => p.Execute(func)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + #endregion + + #region Timeout operation - optimistic + + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - [Fact] - public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() + policy.Invoking(p => p.Execute(ct => { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().Throw(); + } - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - Func func = () => + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; + + Action act = () => { + result = policy.Execute(ct => { - Action action1 = () => { throw innerException1; }; - Action action2 = () => throw innerException2; - Parallel.Invoke(action1, action2); - return ResultPrimitive.WhateverButTooLate; - }; + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - func.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - policy.Invoking(p => p.Execute(func)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + [Fact] + public void Should_throw_timeout_after_correct_duration__optimistic() + { + Stopwatch watch = new Stopwatch(); - #endregion + TimeSpan timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - #region Timeout operation - optimistic + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + watch.Start(); + policy.Invoking(p => p.Execute(ct => { - var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) + SystemClock.Sleep(TimeSpan.FromSeconds(10), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) .Should().Throw(); - } + watch.Stop(); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; - - Action act = () => { - result = policy.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; - - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); + } - [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() - { - Stopwatch watch = new Stopwatch(); + [Fact] + public void Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - TimeSpan timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + } - TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + #endregion - watch.Start(); - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(10), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().Throw(); - watch.Stop(); + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - watch.Elapsed.Should().BeCloseTo(timeout, TimeSpan.FromMilliseconds(tolerance.TotalMilliseconds)); - } + [Fact] + public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + { + int timeout = 5; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + policy.Invoking(p => p.Execute( + _ => { + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + return ResultPrimitive.WhateverButTooLate; + }, userTokenSource.Token) + ).Should().Throw(); // ... it's still the timeout we expect. } + } - #endregion + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + { + var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + bool executed = false; - [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - int timeout = 5; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + cts.Cancel(); - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + policy.Invoking(p => p.Execute(_ => { - policy.Invoking(p => p.Execute( - _ => { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - return ResultPrimitive.WhateverButTooLate; - }, userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. - } + executed = true; + return ResultPrimitive.WhateverButTooLate; + }, cts.Token)) + .Should().Throw(); } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() - { - var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); + executed.Should().BeFalse(); + } - bool executed = false; + #endregion - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - policy.Invoking(p => p.Execute(_ => - { - executed = true; + [Fact] + public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + { + int timeout = 10; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + { + policy.Invoking(p => p.Execute( + ct => { + userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution return ResultPrimitive.WhateverButTooLate; - }, cts.Token)) - .Should().Throw(); - } - - executed.Should().BeFalse(); + }, userTokenSource.Token) // ... with user token. + ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. } + } - #endregion + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) + bool executed = false; - [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() + using (CancellationTokenSource cts = new CancellationTokenSource()) { - int timeout = 10; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); - using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) + cts.Cancel(); + + policy.Invoking(p => p.Execute(_ => { - policy.Invoking(p => p.Execute( - ct => { - userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution - return ResultPrimitive.WhateverButTooLate; - }, userTokenSource.Token) // ... with user token. - ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. - } + executed = true; + return ResultPrimitive.WhateverButTooLate; + }, cts.Token)) + .Should().Throw(); } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + executed.Should().BeFalse(); + } + + #endregion + + #region onTimeout overload - pessimistic + + [Fact] + public void Should_call_ontimeout_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => { - var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().Throw(); - bool executed = false; + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - using (CancellationTokenSource cts = new CancellationTokenSource()) - { - cts.Cancel(); + [Fact] + public void Should_call_ontimeout_with_passed_context__pessimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - policy.Invoking(p => p.Execute(_ => - { - executed = true; - return ResultPrimitive.WhateverButTooLate; - }, cts.Token)) - .Should().Throw(); - } + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - executed.Should().BeFalse(); - } + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - #endregion + policy.Invoking(p => p.Execute(_ => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute)) + .Should().Throw(); - #region onTimeout overload - pessimistic + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => + policy.Invoking(p => p.Execute(() => { SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); + } - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(_ => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute)) - .Should().Throw(); + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + policy.Invoking(p => p.Execute(_ => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, context)) + .Should().Throw(); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().Throw(); - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().Throw(); + taskPassedToOnTimeout.Should().NotBeNull(); + } - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. + // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - } + Exception exceptionToThrow = new DivideByZeroException(); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + throw exceptionToThrow; + })) + .Should().Throw(); - policy.Invoking(p => p.Execute(_ => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, context)) - .Should().Throw(); + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => + policy.Invoking(p => p.Execute(() => { SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().Throw(); - taskPassedToOnTimeout.Should().NotBeNull(); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. - // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + #endregion - Exception exceptionToThrow = new DivideByZeroException(); - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); - }; + #region onTimeout overload - optimistic - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - throw exceptionToThrow; - })) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - } + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + policy.Invoking(p => p.Execute(ct => { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; + SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().Throw(); - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_with_passed_context__optimistic() + { + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - #endregion + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + policy.Invoking(p => p.Execute((_, ct) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute, userCancellationToken)) + .Should().Throw(); - #region onTimeout overload - optimistic + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => + policy.Invoking(p => p.Execute(ct => { - SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() - { - string operationKey = "SomeKey"; - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - policy.Invoking(p => p.Execute((_, ct) => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute, userCancellationToken)) - .Should().Throw(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + policy.Invoking(p => p.Execute((_, ct) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, context, userCancellationToken)) + .Should().Throw(); - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().Throw(); + [Fact] + public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + policy.Invoking(p => p.Execute(ct => { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().Throw(); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; - - policy.Invoking(p => p.Execute((_, ct) => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, context, userCancellationToken)) - .Should().Throw(); + taskPassedToOnTimeout.Should().BeNull(); + } - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => + policy.Invoking(p => p.Execute(ct => { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().Throw(); - taskPassedToOnTimeout.Should().BeNull(); - } - - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().Throw(); - - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion - } } diff --git a/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs b/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs index 6a1f703d300..8f18318b9a7 100644 --- a/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs +++ b/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs @@ -8,264 +8,263 @@ using Polly.NoOp; using Polly.Retry; -namespace Polly.Specs.Wrap +namespace Polly.Specs.Wrap; + +public class IPolicyWrapExtensionSpecs { - public class IPolicyWrapExtensionSpecs + + [Fact] + public void Should_pass_all_nested_policies_from_PolicyWrap_in_same_order_they_were_added() + { + NoOpPolicy policy0 = Policy.NoOp(); + NoOpPolicy policy1 = Policy.NoOp(); + NoOpPolicy policy2 = Policy.NoOp(); + + PolicyWrap policyWrap = Policy.Wrap(policy0, policy1, policy2); + + List policies = policyWrap.GetPolicies().ToList(); + policies.Count.Should().Be(3); + policies[0].Should().Be(policy0); + policies[1].Should().Be(policy1); + policies[2].Should().Be(policy2); + } + + [Fact] + public void Should_return_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + PolicyWrap wrap = Policy.Wrap(policyA, policyB); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void Threepolicies_by_static_sequence_should_return_correct_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = Policy.Wrap(policyA, policyB, policyC); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void Threepolicies_lefttree_should_return_correct_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB).Wrap(policyC); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void Threepolicies_righttree_should_return_correct_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_single_policy_of_type_TPolicy() { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyB }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_empty_enumerable_if_no_policy_of_type_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEmpty(); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_multiple_policies_of_type_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_policies_of_type_TPolicy_matching_predicate() + { + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + policyA.Isolate(); + + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies(p => p.CircuitState == CircuitState.Closed).Should().BeEquivalentTo(new[] { policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_empty_enumerable_if_none_match_predicate() + { + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies(p => p.CircuitState == CircuitState.Open).Should().BeEmpty(); + } + + [Fact] + public void GetPoliciesTPolicy_with_predicate_should_return_multiple_policies_of_type_TPolicy_if_multiple_match_predicate() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies(_ => true).Should().BeEquivalentTo(new[] { policyA, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_with_predicate_should_throw_if_predicate_is_null() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB); + + Action configure = () => wrap.GetPolicies(null); + + configure.Should().Throw().And.ParamName.Should().Be("filter"); + } + + [Fact] + public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy().Should().BeSameAs(policyB); + } + + [Fact] + public void GetPolicyTPolicy_should_return_null_if_no_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy().Should().BeNull(); + } + + [Fact] + public void GetPolicyTPolicy_should_throw_if_multiple_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.Invoking(p => p.GetPolicy()).Should().Throw(); + } + + [Fact] + public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy_matching_predicate() + { + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + policyA.Isolate(); + + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy(p => p.CircuitState == CircuitState.Closed).Should().BeSameAs(policyC); + } + + [Fact] + public void GetPolicyTPolicy_should_return_null_if_none_match_predicate() + { + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy(p => p.CircuitState == CircuitState.Open).Should().BeNull(); + } + + [Fact] + public void GetPolicyTPolicy_with_predicate_should_throw_if_multiple_TPolicy_if_multiple_match_predicate() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.Invoking(p => p.GetPolicy(_ => true)).Should().Throw(); + } + + [Fact] + public void GetPolicyTPolicy_with_predicate_should_throw_if_predicate_is_null() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + PolicyWrap wrap = policyA.Wrap(policyB); + + Action configure = () => wrap.GetPolicy(null); - [Fact] - public void Should_pass_all_nested_policies_from_PolicyWrap_in_same_order_they_were_added() - { - NoOpPolicy policy0 = Policy.NoOp(); - NoOpPolicy policy1 = Policy.NoOp(); - NoOpPolicy policy2 = Policy.NoOp(); - - PolicyWrap policyWrap = Policy.Wrap(policy0, policy1, policy2); - - List policies = policyWrap.GetPolicies().ToList(); - policies.Count.Should().Be(3); - policies[0].Should().Be(policy0); - policies[1].Should().Be(policy1); - policies[2].Should().Be(policy2); - } - - [Fact] - public void Should_return_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - PolicyWrap wrap = Policy.Wrap(policyA, policyB); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void Threepolicies_by_static_sequence_should_return_correct_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = Policy.Wrap(policyA, policyB, policyC); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void Threepolicies_lefttree_should_return_correct_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB).Wrap(policyC); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void Threepolicies_righttree_should_return_correct_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_single_policy_of_type_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyB }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_empty_enumerable_if_no_policy_of_type_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEmpty(); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_multiple_policies_of_type_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_policies_of_type_TPolicy_matching_predicate() - { - CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - policyA.Isolate(); - - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies(p => p.CircuitState == CircuitState.Closed).Should().BeEquivalentTo(new[] { policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_empty_enumerable_if_none_match_predicate() - { - CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies(p => p.CircuitState == CircuitState.Open).Should().BeEmpty(); - } - - [Fact] - public void GetPoliciesTPolicy_with_predicate_should_return_multiple_policies_of_type_TPolicy_if_multiple_match_predicate() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies(_ => true).Should().BeEquivalentTo(new[] { policyA, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_with_predicate_should_throw_if_predicate_is_null() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB); - - Action configure = () => wrap.GetPolicies(null); - - configure.Should().Throw().And.ParamName.Should().Be("filter"); - } - - [Fact] - public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy().Should().BeSameAs(policyB); - } - - [Fact] - public void GetPolicyTPolicy_should_return_null_if_no_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy().Should().BeNull(); - } - - [Fact] - public void GetPolicyTPolicy_should_throw_if_multiple_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.Invoking(p => p.GetPolicy()).Should().Throw(); - } - - [Fact] - public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy_matching_predicate() - { - CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - policyA.Isolate(); - - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy(p => p.CircuitState == CircuitState.Closed).Should().BeSameAs(policyC); - } - - [Fact] - public void GetPolicyTPolicy_should_return_null_if_none_match_predicate() - { - CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy(p => p.CircuitState == CircuitState.Open).Should().BeNull(); - } - - [Fact] - public void GetPolicyTPolicy_with_predicate_should_throw_if_multiple_TPolicy_if_multiple_match_predicate() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.Invoking(p => p.GetPolicy(_ => true)).Should().Throw(); - } - - [Fact] - public void GetPolicyTPolicy_with_predicate_should_throw_if_predicate_is_null() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB); - - Action configure = () => wrap.GetPolicy(null); - - configure.Should().Throw().And.ParamName.Should().Be("filter"); - } + configure.Should().Throw().And.ParamName.Should().Be("filter"); } } diff --git a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs index c06672241da..fda74b9f571 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs @@ -3,280 +3,279 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Wrap +namespace Polly.Specs.Wrap; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class PolicyWrapContextAndKeySpecs { - [Collection(Constants.SystemClockDependentTestCollection)] - public class PolicyWrapContextAndKeySpecs + #region PolicyKey and execution Context tests + + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - #region PolicyKey and execution Context tests + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - string policyWrapKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + wrap.RaiseException(1); - wrap.RaiseException(1); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => {}; - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => {}; + var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + wrap.RaiseException(1); - wrap.RaiseException(1); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + ISyncPolicy fallback = Policy + .Handle() + .Fallback(_ => {}, onFallback: (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + }) + .WithPolicyKey("FallbackPolicy"); + + ISyncPolicy retry = Policy + .Handle() + .Retry(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); - [Fact] - public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - ISyncPolicy fallback = Policy - .Handle() - .Fallback(_ => {}, onFallback: (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - }) - .WithPolicyKey("FallbackPolicy"); - - ISyncPolicy retry = Policy - .Handle() - .Retry(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - ISyncPolicy policyWrap = Policy.Wrap(fallback, retry) - .WithPolicyKey("PolicyWrap"); - - policyWrap.Execute(() => throw new Exception()); - } - - [Fact] - public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() + ISyncPolicy policyWrap = Policy.Wrap(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.Execute(() => throw new Exception()); + } + + [Fact] + public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string fallbackKey = Guid.NewGuid().ToString(); - string innerWrapKey = Guid.NewGuid().ToString(); - string outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); - var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); - outerWrap.RaiseException(1); + outerWrap.RaiseException(1); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } - [Fact] - public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() + [Fact] + public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string fallbackKey = Guid.NewGuid().ToString(); - string innerWrapKey = Guid.NewGuid().ToString(); - string outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); - var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); - bool doneOnceOnly = false; - outerWrap.Execute(() => + bool doneOnceOnly = false; + outerWrap.Execute(() => + { + if (!doneOnceOnly) { - if (!doneOnceOnly) - { - doneOnceOnly = true; - throw new Exception(); - } - }); - - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } - - #endregion - + doneOnceOnly = true; + throw new Exception(); + } + }); + + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); } - [Collection(Constants.SystemClockDependentTestCollection)] - public class PolicyWrapTResultContextAndKeySpecs + #endregion + +} + +[Collection(Constants.SystemClockDependentTestCollection)] +public class PolicyWrapTResultContextAndKeySpecs +{ + #region PolicyKey and execution Context tests + + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - #region PolicyKey and execution Context tests + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - string policyWrapKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => { }; - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => { }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - [Fact] - public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - ISyncPolicy fallback = Policy - .Handle() - .Fallback(ResultPrimitive.Undefined, onFallback: (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - }) - .WithPolicyKey("FallbackPolicy"); - - ISyncPolicy retry = Policy - .Handle() - .Retry(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - Policy policyWrap = Policy.Wrap(fallback, retry) - .WithPolicyKey("PolicyWrap"); - - policyWrap.Execute(() => throw new Exception()); - } - - [Fact] - public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() - { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string fallbackKey = Guid.NewGuid().ToString(); - string innerWrapKey = Guid.NewGuid().ToString(); - string outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + ISyncPolicy fallback = Policy + .Handle() + .Fallback(ResultPrimitive.Undefined, onFallback: (_, context) => { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + }) + .WithPolicyKey("FallbackPolicy"); + + ISyncPolicy retry = Policy + .Handle() + .Retry(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + Policy policyWrap = Policy.Wrap(fallback, retry) + .WithPolicyKey("PolicyWrap"); - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.HandleResult(ResultPrimitive.Fault).Fallback(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); + policyWrap.Execute(() => throw new Exception()); + } - var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); + [Fact] + public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - outerWrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.HandleResult(ResultPrimitive.Fault).Fallback(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); - #endregion + outerWrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); } + #endregion + } + diff --git a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs index 24254002f2f..34e0e956ec2 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs @@ -5,346 +5,345 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Wrap +namespace Polly.Specs.Wrap; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class PolicyWrapContextAndKeySpecsAsync { - [Collection(Constants.SystemClockDependentTestCollection)] - public class PolicyWrapContextAndKeySpecsAsync + #region PolicyKey and execution Context tests + + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - #region PolicyKey and execution Context tests + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - string policyWrapKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + await wrap.RaiseExceptionAsync(1); - await wrap.RaiseExceptionAsync(1); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => { }; + + var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + + await wrap.RaiseExceptionAsync(1); + + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => { }; + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); - var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); - await wrap.RaiseExceptionAsync(1); + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + await policyWrap.ExecuteAsync(() => throw new Exception()); + } - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); - - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); - - await policyWrap.ExecuteAsync(() => throw new Exception()); - } - - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); - - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); - - await policyWrap.ExecuteAsync(async () => await Task.Run(() => throw new Exception())); // Regression test for issue 510 - } - - [Fact] - public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() - { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string fallbackKey = Guid.NewGuid().ToString(); - string innerWrapKey = Guid.NewGuid().ToString(); - string outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + await policyWrap.ExecuteAsync(async () => await Task.Run(() => throw new Exception())); // Regression test for issue 510 + } + + [Fact] + public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); - var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); - await outerWrap.RaiseExceptionAsync(1); + await outerWrap.RaiseExceptionAsync(1); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } - [Fact] - public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() + [Fact] + public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string fallbackKey = Guid.NewGuid().ToString(); - string innerWrapKey = Guid.NewGuid().ToString(); - string outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); - var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); - bool doneOnceOnly = false; - await outerWrap.ExecuteAsync(() => + bool doneOnceOnly = false; + await outerWrap.ExecuteAsync(() => + { + if (!doneOnceOnly) { - if (!doneOnceOnly) - { - doneOnceOnly = true; - throw new Exception(); - } - return TaskHelper.EmptyTask; - }); + doneOnceOnly = true; + throw new Exception(); + } + return TaskHelper.EmptyTask; + }); + + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + #endregion - #endregion +} - } +[Collection(Constants.SystemClockDependentTestCollection)] +public class PolicyWrapTResultContextAndKeySpecsAsync +{ + #region PolicyKey and execution Context tests - [Collection(Constants.SystemClockDependentTestCollection)] - public class PolicyWrapTResultContextAndKeySpecsAsync + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - #region PolicyKey and execution Context tests + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - string policyWrapKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => { }; + + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => + await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } + + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => { }; + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); - await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + await policyWrap.ExecuteAsync(() => throw new Exception()); + } - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); - - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); - - await policyWrap.ExecuteAsync(() => throw new Exception()); - } - - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); - - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); - - await policyWrap.ExecuteAsync(async () => await Task.Run(() => // Regression test for issue 510 + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => { - throw new Exception(); -#pragma warning disable 0162 // unreachable code detected - return ResultPrimitive.WhateverButTooLate; -#pragma warning restore 0162 + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); - })); - } + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); - [Fact] - public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() + await policyWrap.ExecuteAsync(async () => await Task.Run(() => // Regression test for issue 510 { - string retryKey = Guid.NewGuid().ToString(); - string breakerKey = Guid.NewGuid().ToString(); - string fallbackKey = Guid.NewGuid().ToString(); - string innerWrapKey = Guid.NewGuid().ToString(); - string outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + throw new Exception(); +#pragma warning disable 0162 // unreachable code detected + return ResultPrimitive.WhateverButTooLate; +#pragma warning restore 0162 - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.HandleResult(ResultPrimitive.Fault).FallbackAsync(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); + })); + } - var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); + [Fact] + public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() + { + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - await outerWrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.HandleResult(ResultPrimitive.Fault).FallbackAsync(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); - #endregion + await outerWrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); } + #endregion + } + diff --git a/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs b/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs index 1b0eca7b5b6..304554475ef 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs @@ -6,570 +6,569 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Wrap +namespace Polly.Specs.Wrap; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class PolicyWrapSpecs { - [Collection(Constants.SystemClockDependentTestCollection)] - public class PolicyWrapSpecs + #region Instance configuration syntax tests, non-generic outer + + [Fact] + public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() { - #region Instance configuration syntax tests, non-generic outer + RetryPolicy retry = Policy.Handle().Retry(1); - [Fact] - public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - RetryPolicy retry = Policy.Handle().Retry(1); + Action config = () => retry.Wrap((Policy)null); - Action config = () => retry.Wrap((Policy)null); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + RetryPolicy retry = Policy.Handle().Retry(1); + + Action config = () => retry.Wrap((Policy)null); + + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - RetryPolicy retry = Policy.Handle().Retry(1); + [Fact] + public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - Action config = () => retry.Wrap((Policy)null); + PolicyWrap wrap = policyA.Wrap(policyB); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + [Fact] + public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - PolicyWrap wrap = policyA.Wrap(policyB); + PolicyWrap wrap = policyA.Wrap(policyB); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + #endregion - PolicyWrap wrap = policyA.Wrap(policyB); + #region Instance configuration syntax tests, generic outer - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + RetryPolicy retry = Policy.HandleResult(0).Retry(1); - #endregion + Action config = () => retry.Wrap((Policy)null); - #region Instance configuration syntax tests, generic outer + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - RetryPolicy retry = Policy.HandleResult(0).Retry(1); - Action config = () => retry.Wrap((Policy)null); + [Fact] + public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + RetryPolicy retry = Policy.HandleResult(0).Retry(1); + + Action config = () => retry.Wrap((Policy)null); + + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } + + [Fact] + public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + + PolicyWrap wrap = policyA.Wrap(policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } + + [Fact] + public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + + PolicyWrap wrap = policyA.Wrap(policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + #endregion + #region Interface extension configuration syntax tests, non-generic outer - [Fact] - public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - RetryPolicy retry = Policy.HandleResult(0).Retry(1); + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.Handle().Retry(1); + + Action config = () => outerNull.Wrap(retry); + + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + + Action config = () => outerNull.Wrap(retry); + + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.Handle().Retry(1); + + Action config = () => retry.Wrap((Policy)null); + + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.Handle().Retry(1); + + Action config = () => retry.Wrap((Policy)null); + + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); + + IPolicyWrap wrap = policyA.Wrap(policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } + + [Fact] + public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); - Action config = () => retry.Wrap((Policy)null); + IPolicyWrap wrap = policyA.Wrap(policyB); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + #endregion - PolicyWrap wrap = policyA.Wrap(policyB); + #region Interface extension configuration syntax tests, generic outer - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.Handle().Retry(1); - [Fact] - public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + Action config = () => outerNull.Wrap(retry); - PolicyWrap wrap = policyA.Wrap(policyB); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); - #endregion + Action config = () => outerNull.Wrap(retry); - #region Interface extension configuration syntax tests, non-generic outer + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.Handle().Retry(1); + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); - Action config = () => outerNull.Wrap(retry); + Action config = () => retry.Wrap((Policy)null); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); - Action config = () => outerNull.Wrap(retry); + Action config = () => retry.Wrap((Policy)null); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.Handle().Retry(1); + [Fact] + public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); - Action config = () => retry.Wrap((Policy)null); + IPolicyWrap wrap = policyA.Wrap(policyB); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.Handle().Retry(1); + [Fact] + public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); - Action config = () => retry.Wrap((Policy)null); + IPolicyWrap wrap = policyA.Wrap(policyB); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + #endregion - IPolicyWrap wrap = policyA.Wrap(policyB); + #region Static configuration syntax tests, non-generic policies - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Wrapping_nothing_using_static_wrap_syntax_should_throw() + { + Action config = () => Policy.Wrap(); - [Fact] - public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - IPolicyWrap wrap = policyA.Wrap(policyB); + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() + { + Policy singlePolicy = Policy.Handle().Retry(); + Action config = () => Policy.Wrap(new[] {singlePolicy}); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - #endregion + [Fact] + public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.Wrap(new[] {retry, breaker}); - #region Interface extension configuration syntax tests, generic outer + config.Should().NotThrow(); + } - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.Handle().Retry(1); + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(1); + Policy divideByZeroRetry = Policy.Handle().Retry(2); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - Action config = () => outerNull.Wrap(retry); + Action config = () => Policy.Wrap(new[] {divideByZeroRetry, retry, breaker}); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + config.Should().NotThrow(); + } - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + [Fact] + public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - Action config = () => outerNull.Wrap(retry); + PolicyWrap wrap = Policy.Wrap(policyA, policyB); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + #endregion - Action config = () => retry.Wrap((Policy)null); + #region Static configuration syntax tests, strongly-typed policies - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() + { + Action config = () => Policy.Wrap(); - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - Action config = () => retry.Wrap((Policy)null); + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() + { + Policy singlePolicy = Policy.Handle().Retry(); + Action config = () => Policy.Wrap(new[] { singlePolicy }); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - [Fact] - public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + [Fact] + public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.Wrap(new[] { retry, breaker }); - IPolicyWrap wrap = policyA.Wrap(policyB); + config.Should().NotThrow(); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(); + Policy divideByZeroRetry = Policy.Handle().Retry(2); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - [Fact] - public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + Action config = () => Policy.Wrap(new[] { divideByZeroRetry, retry, breaker }); - IPolicyWrap wrap = policyA.Wrap(policyB); + config.Should().NotThrow(); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - #endregion + PolicyWrap wrap = Policy.Wrap(policyA, policyB); - #region Static configuration syntax tests, non-generic policies + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Wrapping_nothing_using_static_wrap_syntax_should_throw() - { - Action config = () => Policy.Wrap(); + #endregion - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + #region Instance-configured: execution tests - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() - { - Policy singlePolicy = Policy.Handle().Retry(); - Action config = () => Policy.Wrap(new[] {singlePolicy}); + [Fact] + public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + RetryPolicy retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); + + PolicyWrap retryWrappingBreaker = retry.Wrap(breaker); + PolicyWrap breakerWrappingRetry = breaker.Wrap(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + [Fact] + public void Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + RetryPolicy retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = retry.Wrap(breaker); + var breakerWrappingRetry = breaker.Wrap(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.Wrap(new[] {retry, breaker}); + #endregion - config.Should().NotThrow(); - } + #region Static-configured: execution tests - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(1); - Policy divideByZeroRetry = Policy.Handle().Retry(2); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); + [Fact] + public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + RetryPolicy retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); + + PolicyWrap retryWrappingBreaker = Policy.Wrap(retry, breaker); + PolicyWrap breakerWrappingRetry = Policy.Wrap(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - Action config = () => Policy.Wrap(new[] {divideByZeroRetry, retry, breaker}); + [Fact] + public void Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + RetryPolicy retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = Policy.Wrap(retry, breaker); + var breakerWrappingRetry = Policy.Wrap(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - config.Should().NotThrow(); - } + #endregion - [Fact] - public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + #region ExecuteAndCaptureSpecs - PolicyWrap wrap = Policy.Wrap(policyA, policyB); + [Fact] + public void Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + CircuitBreakerPolicy innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + CircuitBreakerPolicy innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + } - #endregion - - #region Static configuration syntax tests, strongly-typed policies + [Fact] + public void Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + CircuitBreakerPolicy innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); + } - [Fact] - public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() - { - Action config = () => Policy.Wrap(); + [Fact] + public void Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + CircuitBreakerPolicy innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } - - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() - { - Policy singlePolicy = Policy.Handle().Retry(); - Action config = () => Policy.Wrap(new[] { singlePolicy }); + [Fact] + public void Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() + { + CircuitBreakerPolicy innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.Zero); + PolicyWrap wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); + + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.Fault); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } - - [Fact] - public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.Wrap(new[] { retry, breaker }); - - config.Should().NotThrow(); - } - - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(); - Policy divideByZeroRetry = Policy.Handle().Retry(2); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - - Action config = () => Policy.Wrap(new[] { divideByZeroRetry, retry, breaker }); - - config.Should().NotThrow(); - } - - [Fact] - public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - - PolicyWrap wrap = Policy.Wrap(policyA, policyB); - - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } - - #endregion - - #region Instance-configured: execution tests - - [Fact] - public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - RetryPolicy retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. - CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); - - PolicyWrap retryWrappingBreaker = retry.Wrap(breaker); - PolicyWrap breakerWrappingRetry = breaker.Wrap(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - RetryPolicy retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. - CircuitBreakerPolicy breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = retry.Wrap(breaker); - var breakerWrappingRetry = breaker.Wrap(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region Static-configured: execution tests - - [Fact] - public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - RetryPolicy retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. - CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); - - PolicyWrap retryWrappingBreaker = Policy.Wrap(retry, breaker); - PolicyWrap breakerWrappingRetry = Policy.Wrap(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - RetryPolicy retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. - CircuitBreakerPolicy breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = Policy.Wrap(retry, breaker); - var breakerWrappingRetry = Policy.Wrap(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region ExecuteAndCaptureSpecs - - [Fact] - public void Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - CircuitBreakerPolicy innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - CircuitBreakerPolicy outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - } - - [Fact] - public void Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - CircuitBreakerPolicy innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - CircuitBreakerPolicy outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - } - - [Fact] - public void Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - CircuitBreakerPolicy innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - CircuitBreakerPolicy outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); - } - - [Fact] - public void Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - CircuitBreakerPolicy innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - CircuitBreakerPolicy outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); - } - - [Fact] - public void Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() - { - CircuitBreakerPolicy innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(1, TimeSpan.Zero); - CircuitBreakerPolicy outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.Zero); - PolicyWrap wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); - - PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.Fault); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } - - [Fact] - public void Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() - { - CircuitBreakerPolicy innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(1, TimeSpan.Zero); - CircuitBreakerPolicy outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.Zero); - PolicyWrap wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); - - PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.FaultAgain); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } - - #endregion + [Fact] + public void Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() + { + CircuitBreakerPolicy innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.Zero); + PolicyWrap wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); + + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.FaultAgain); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); } + + #endregion } diff --git a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs index dcd7d3f0493..72015e808a8 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs @@ -6,572 +6,571 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Wrap +namespace Polly.Specs.Wrap; + +[Collection(Constants.SystemClockDependentTestCollection)] +public class PolicyWrapSpecsAsync { - [Collection(Constants.SystemClockDependentTestCollection)] - public class PolicyWrapSpecsAsync + #region Instance configuration syntax tests, non-generic outer + + [Fact] + public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() { - #region Instance configuration syntax tests, non-generic outer + var retry = Policy.Handle().RetryAsync(1); - [Fact] - public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.Handle().RetryAsync(1); + Action config = () => retry.WrapAsync((AsyncPolicy)null); - Action config = () => retry.WrapAsync((AsyncPolicy)null); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.Handle().RetryAsync(1); - [Fact] - public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.Handle().RetryAsync(1); + Action config = () => retry.WrapAsync((AsyncPolicy)null); - Action config = () => retry.WrapAsync((AsyncPolicy)null); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } + + [Fact] + public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); - [Fact] - public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); + [Fact] + public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); - [Fact] - public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); + #endregion - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + #region Instance configuration syntax tests, generic outer - #endregion + [Fact] + public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.HandleResult(0).RetryAsync(1); - #region Instance configuration syntax tests, generic outer + Action config = () => retry.WrapAsync((AsyncPolicy)null); - [Fact] - public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.HandleResult(0).RetryAsync(1); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - Action config = () => retry.WrapAsync((AsyncPolicy)null); + [Fact] + public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.HandleResult(0).RetryAsync(1); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + Action config = () => retry.WrapAsync((AsyncPolicy)null); - [Fact] - public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.HandleResult(0).RetryAsync(1); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - Action config = () => retry.WrapAsync((AsyncPolicy)null); + [Fact] + public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); + + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } + + [Fact] + public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); + + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } + + #endregion + + #region Interface extension configuration syntax tests, non-generic outer + + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + Action config = () => outerNull.WrapAsync(retry); - [Fact] - public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + + Action config = () => outerNull.WrapAsync(retry); + + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + + Action config = () => retry.WrapAsync((AsyncPolicy)null); + + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + + Action config = () => retry.WrapAsync((AsyncPolicy)null); + + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } + + [Fact] + public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); + + IPolicyWrap wrap = policyA.WrapAsync(policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } + + [Fact] + public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); + + IPolicyWrap wrap = policyA.WrapAsync(policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); + #endregion - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + #region Interface extension configuration syntax tests, generic outer - [Fact] - public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); + Action config = () => outerNull.WrapAsync(retry); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - #endregion + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); - #region Interface extension configuration syntax tests, non-generic outer + Action config = () => outerNull.WrapAsync(retry); - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - Action config = () => outerNull.WrapAsync(retry); + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + Action config = () => retry.WrapAsync((AsyncPolicy)null); - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - Action config = () => outerNull.WrapAsync(retry); + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + Action config = () => retry.WrapAsync((AsyncPolicy)null); - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - Action config = () => retry.WrapAsync((AsyncPolicy)null); + [Fact] + public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + IPolicyWrap wrap = policyA.WrapAsync(policyB); - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - Action config = () => retry.WrapAsync((AsyncPolicy)null); + [Fact] + public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + IPolicyWrap wrap = policyA.WrapAsync(policyB); - [Fact] - public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - IPolicyWrap wrap = policyA.WrapAsync(policyB); + #endregion - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + #region Static configuration syntax tests, non-generic policies - [Fact] - public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); + [Fact] + public void Wrapping_nothing_using_static_wrap_syntax_should_throw() + { + Action config = () => Policy.WrapAsync(); - IPolicyWrap wrap = policyA.WrapAsync(policyB); + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() + { + AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); + Action config = () => Policy.WrapAsync(new[] { singlePolicy }); - #endregion + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - #region Interface extension configuration syntax tests, generic outer + [Fact] + public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.WrapAsync(new[] { retry, breaker }); - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + config.Should().NotThrow(); + } - Action config = () => outerNull.WrapAsync(retry); + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(1); + AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + config.Should().NotThrow(); + } - Action config = () => outerNull.WrapAsync(retry); + [Fact] + public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + AsyncPolicyWrap wrap = Policy.WrapAsync(policyA, policyB); - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - Action config = () => retry.WrapAsync((AsyncPolicy)null); + #endregion - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + #region Static configuration syntax tests, strongly-typed policies - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + [Fact] + public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() + { + Action config = () => Policy.WrapAsync(); - Action config = () => retry.WrapAsync((AsyncPolicy)null); + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() + { + AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); + Action config = () => Policy.WrapAsync(new[] { singlePolicy }); - [Fact] - public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - IPolicyWrap wrap = policyA.WrapAsync(policyB); + [Fact] + public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.WrapAsync(new[] { retry, breaker }); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + config.Should().NotThrow(); + } - [Fact] - public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(); + AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - IPolicyWrap wrap = policyA.WrapAsync(policyB); + Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + config.Should().NotThrow(); + } - #endregion + [Fact] + public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - #region Static configuration syntax tests, non-generic policies + AsyncPolicyWrap wrap = Policy.WrapAsync(policyA, policyB); - [Fact] - public void Wrapping_nothing_using_static_wrap_syntax_should_throw() - { - Action config = () => Policy.WrapAsync(); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + #endregion - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() - { - AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); - Action config = () => Policy.WrapAsync(new[] { singlePolicy }); + #region Instance-configured: execution tests - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + [Fact] + public async Task Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); + + AsyncPolicyWrap retryWrappingBreaker = retry.WrapAsync(breaker); + AsyncPolicyWrap breakerWrappingRetry = breaker.WrapAsync(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + await retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + await breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.WrapAsync(new[] { retry, breaker }); + [Fact] + public async Task Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = retry.WrapAsync(breaker); + var breakerWrappingRetry = breaker.WrapAsync(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - config.Should().NotThrow(); - } + #endregion - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(1); - AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + #region Static-configured: execution tests - Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); + [Fact] + public async Task Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); + + AsyncPolicyWrap retryWrappingBreaker = Policy.WrapAsync(retry, breaker); + AsyncPolicyWrap breakerWrappingRetry = Policy.WrapAsync(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + await retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + await breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().ThrowAsync(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - config.Should().NotThrow(); - } + [Fact] + public async Task Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = Policy.WrapAsync(retry, breaker); + var breakerWrappingRetry = Policy.WrapAsync(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + #endregion - AsyncPolicyWrap wrap = Policy.WrapAsync(policyA, policyB); + #region ExecuteAndCaptureAsyncSpecs - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public async Task Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = + await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + } - #endregion - - #region Static configuration syntax tests, strongly-typed policies + [Fact] + public async Task Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = + await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + } - [Fact] - public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() - { - Action config = () => Policy.WrapAsync(); + [Fact] + public async Task Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } - - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() - { - AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); - Action config = () => Policy.WrapAsync(new[] { singlePolicy }); + [Fact] + public async Task Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } - - [Fact] - public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.WrapAsync(new[] { retry, breaker }); - - config.Should().NotThrow(); - } - - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(); - AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - - Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); - - config.Should().NotThrow(); - } - - [Fact] - public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); - - AsyncPolicyWrap wrap = Policy.WrapAsync(policyA, policyB); - - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } - - #endregion - - #region Instance-configured: execution tests - - [Fact] - public async Task Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); - - AsyncPolicyWrap retryWrappingBreaker = retry.WrapAsync(breaker); - AsyncPolicyWrap breakerWrappingRetry = breaker.WrapAsync(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - await retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - await breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public async Task Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = retry.WrapAsync(breaker); - var breakerWrappingRetry = breaker.WrapAsync(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region Static-configured: execution tests - - [Fact] - public async Task Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); - - AsyncPolicyWrap retryWrappingBreaker = Policy.WrapAsync(retry, breaker); - AsyncPolicyWrap breakerWrappingRetry = Policy.WrapAsync(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - await retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - await breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().ThrowAsync(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public async Task Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = Policy.WrapAsync(retry, breaker); - var breakerWrappingRetry = Policy.WrapAsync(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region ExecuteAndCaptureAsyncSpecs - - [Fact] - public async Task Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = - await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - } - - [Fact] - public async Task Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = - await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - } - - [Fact] - public async Task Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); - } - - [Fact] - public async Task Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); - } - - [Fact] - public async Task Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() - { - var innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.Zero); - AsyncPolicyWrap wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); - - PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } - - [Fact] - public async Task Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() - { - var innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.Zero); - AsyncPolicyWrap wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); - - PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } - - #endregion + [Fact] + public async Task Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() + { + var innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.Zero); + AsyncPolicyWrap wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); + + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); + } + [Fact] + public async Task Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() + { + var innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.Zero); + AsyncPolicyWrap wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); + + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); } + + #endregion + } diff --git a/src/Polly/AsyncPolicy.ContextAndKeys.cs b/src/Polly/AsyncPolicy.ContextAndKeys.cs index 97d0c87aa03..80a83d51b65 100644 --- a/src/Polly/AsyncPolicy.ContextAndKeys.cs +++ b/src/Polly/AsyncPolicy.ContextAndKeys.cs @@ -1,61 +1,60 @@ -namespace Polly +namespace Polly; + +public abstract partial class AsyncPolicy { - public abstract partial class AsyncPolicy + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public AsyncPolicy WithPolicyKey(string policyKey) { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public AsyncPolicy WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } - - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + policyKeyInternal = policyKey; + return this; } - public abstract partial class AsyncPolicy + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public AsyncPolicy WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } - - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } + +} + +public abstract partial class AsyncPolicy +{ + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public AsyncPolicy WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } + + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; } } diff --git a/src/Polly/AsyncPolicy.ExecuteOverloads.cs b/src/Polly/AsyncPolicy.ExecuteOverloads.cs index 3a43863ab0e..bfc94327e78 100644 --- a/src/Polly/AsyncPolicy.ExecuteOverloads.cs +++ b/src/Polly/AsyncPolicy.ExecuteOverloads.cs @@ -4,477 +4,476 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +public abstract partial class AsyncPolicy : PolicyBase, IAsyncPolicy { - public abstract partial class AsyncPolicy : PolicyBase, IAsyncPolicy + #region ExecuteAsync overloads + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + [DebuggerStepThrough] + public Task ExecuteAsync(Func action) + => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + [DebuggerStepThrough] + public Task ExecuteAsync(Func action, IDictionary contextData) + => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + [DebuggerStepThrough] + public Task ExecuteAsync(Func action, Context context) + => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// 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); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// 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, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// 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, Context context, CancellationToken cancellationToken) + => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// 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); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + [DebuggerStepThrough] + public Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { - #region ExecuteAsync overloads - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - [DebuggerStepThrough] - public Task ExecuteAsync(Func action) - => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - [DebuggerStepThrough] - public Task ExecuteAsync(Func action, IDictionary contextData) - => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - [DebuggerStepThrough] - public Task ExecuteAsync(Func action, Context context) - => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// 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); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// 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, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// 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, Context context, CancellationToken cancellationToken) - => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// 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); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - [DebuggerStepThrough] - public Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - - try - { - await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } + await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } + } - #region Overloads method-generic in TResult - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action) - => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData) - => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context) - => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + #region Overloads method-generic in TResult + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action) + => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData) + => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context) + => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - - try - { - return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } + return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } + } - #endregion - - #endregion - - #region ExecuteAndCaptureAsync overloads - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action) - => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, Context context) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// 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, Context context, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// Please 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + #endregion + + #endregion + + #region ExecuteAndCaptureAsync overloads + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action) + => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, Context context) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// 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, Context context, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// Please 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + + try + { + await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return PolicyResult.Successful(context); + } + catch (Exception exception) { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return PolicyResult.Successful(context); - } - catch (Exception exception) - { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); - } + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } + } - #region Overloads method-generic in TResult - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action) - => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + #region Overloads method-generic in TResult + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action) + => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + + try + { + return PolicyResult.Successful( + await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext), context); + } + catch (Exception exception) { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - return PolicyResult.Successful( - await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext), context); - } - catch (Exception exception) - { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); - } + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } + } - #endregion + #endregion - #endregion + #endregion - } } diff --git a/src/Polly/AsyncPolicy.GenericImplementation.cs b/src/Polly/AsyncPolicy.GenericImplementation.cs index 85b0d27d92b..314d1636a3d 100644 --- a/src/Polly/AsyncPolicy.GenericImplementation.cs +++ b/src/Polly/AsyncPolicy.GenericImplementation.cs @@ -2,23 +2,22 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +public abstract partial class AsyncPolicy { - public abstract partial class AsyncPolicy - { - /// - /// Defines the implementation of a policy for async executions returning . - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// Whether async continuations should continue on a captured context. - /// A representing the result of the execution. - protected abstract Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext - ); - } + /// + /// Defines the implementation of a policy for async executions returning . + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// Whether async continuations should continue on a captured context. + /// A representing the result of the execution. + protected abstract Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext + ); } diff --git a/src/Polly/AsyncPolicy.NonGenericImplementation.cs b/src/Polly/AsyncPolicy.NonGenericImplementation.cs index 8c856542682..1f8b947266f 100644 --- a/src/Polly/AsyncPolicy.NonGenericImplementation.cs +++ b/src/Polly/AsyncPolicy.NonGenericImplementation.cs @@ -3,44 +3,43 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly +namespace Polly; + +public abstract partial class AsyncPolicy { - public abstract partial class AsyncPolicy - { - /// - /// Defines the implementation of a policy for async executions with no return value. - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// Whether async continuations should continue on a captured context. - /// A representing the result of the execution. - protected virtual Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - => ImplementationAsync(async (ctx, token) => - { - await action(ctx, token).ConfigureAwait(continueOnCapturedContext); - return EmptyStruct.Instance; - }, context, cancellationToken, continueOnCapturedContext); + /// + /// Defines the implementation of a policy for async executions with no return value. + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// Whether async continuations should continue on a captured context. + /// A representing the result of the execution. + protected virtual Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + => ImplementationAsync(async (ctx, token) => + { + await action(ctx, token).ConfigureAwait(continueOnCapturedContext); + return EmptyStruct.Instance; + }, context, cancellationToken, continueOnCapturedContext); - /// - /// Defines the implementation of a policy for async executions returning . - /// - /// The type returned by asynchronous executions through the implementation. - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// Whether async continuations should continue on a captured context. - /// A representing the result of the execution. - protected abstract Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext - ); + /// + /// Defines the implementation of a policy for async executions returning . + /// + /// The type returned by asynchronous executions through the implementation. + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// Whether async continuations should continue on a captured context. + /// A representing the result of the execution. + protected abstract Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext + ); - } } diff --git a/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs b/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs index fcfab737da4..93ed40231af 100644 --- a/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs +++ b/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs @@ -4,249 +4,248 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +public abstract partial class AsyncPolicy : IAsyncPolicy { - public abstract partial class AsyncPolicy : IAsyncPolicy - { - #region ExecuteAsync overloads - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action) - => ExecuteAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData) - => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context) - => ExecuteAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - if (context == null) throw new ArgumentNullException(nameof(context)); + #region ExecuteAsync overloads + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action) + => ExecuteAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData) + => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context) + => ExecuteAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - try - { - return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } + try + { + return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); } - - #endregion - - #region ExecuteAndCaptureAsync overloads - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action) - => ExecuteAndCaptureAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// Please 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + finally { - if (context == null) throw new ArgumentNullException(nameof(context)); + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } + } - try - { - TResult result = await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + #endregion + + #region ExecuteAndCaptureAsync overloads + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action) + => ExecuteAndCaptureAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// Please 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + if (context == null) throw new ArgumentNullException(nameof(context)); - if (ResultPredicates.AnyMatch(result)) - { - return PolicyResult.Failure(result, context); - } + try + { + TResult result = await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return PolicyResult.Successful(result, context); - } - catch (Exception exception) + if (ResultPredicates.AnyMatch(result)) { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + return PolicyResult.Failure(result, context); } + + return PolicyResult.Successful(result, context); + } + catch (Exception exception) + { + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } + } - #endregion + #endregion - } } diff --git a/src/Polly/AsyncPolicy.TResult.cs b/src/Polly/AsyncPolicy.TResult.cs index 50a83d7d84b..b8dfbd4555a 100644 --- a/src/Polly/AsyncPolicy.TResult.cs +++ b/src/Polly/AsyncPolicy.TResult.cs @@ -1,30 +1,29 @@ -namespace Polly +namespace Polly; + +/// +/// Transient exception handling policies that can be applied to asynchronous delegates +/// +/// The return type of delegates which may be executed through the policy. +public abstract partial class AsyncPolicy : PolicyBase { /// - /// Transient exception handling policies that can be applied to asynchronous delegates + /// Constructs a new instance of a derived type with the passed and . /// - /// The return type of delegates which may be executed through the policy. - public abstract partial class AsyncPolicy : PolicyBase + /// Predicates indicating which exceptions the policy should handle. + /// Predicates indicating which results the policy should handle. + internal AsyncPolicy( + ExceptionPredicates exceptionPredicates, + ResultPredicates resultPredicates) + : base(exceptionPredicates, resultPredicates) { - /// - /// Constructs a new instance of a derived type with the passed and . - /// - /// Predicates indicating which exceptions the policy should handle. - /// Predicates indicating which results the policy should handle. - internal AsyncPolicy( - ExceptionPredicates exceptionPredicates, - ResultPredicates resultPredicates) - : base(exceptionPredicates, resultPredicates) - { - } + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A indicating which exceptions and results the policy should handle. - protected AsyncPolicy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { - } + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A indicating which exceptions and results the policy should handle. + protected AsyncPolicy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { } } diff --git a/src/Polly/AsyncPolicy.cs b/src/Polly/AsyncPolicy.cs index e4ab8f73e3c..a1c2b7df9c7 100644 --- a/src/Polly/AsyncPolicy.cs +++ b/src/Polly/AsyncPolicy.cs @@ -1,26 +1,25 @@ -namespace Polly +namespace Polly; + +/// +/// Transient exception handling policies that can be applied to asynchronous delegates +/// +public abstract partial class AsyncPolicy { /// - /// Transient exception handling policies that can be applied to asynchronous delegates + /// Constructs a new instance of a derived type with the passed . /// - public abstract partial class AsyncPolicy + /// Predicates indicating which exceptions the policy should handle. + internal AsyncPolicy(ExceptionPredicates exceptionPredicates) + : base(exceptionPredicates) { - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// Predicates indicating which exceptions the policy should handle. - internal AsyncPolicy(ExceptionPredicates exceptionPredicates) - : base(exceptionPredicates) - { - } + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A specifying which exceptions the policy should handle. - protected AsyncPolicy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { - } + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A specifying which exceptions the policy should handle. + protected AsyncPolicy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { } } diff --git a/src/Polly/Bulkhead/AsyncBulkheadEngine.cs b/src/Polly/Bulkhead/AsyncBulkheadEngine.cs index dd6cdf12f1b..e3c3a8f6ec3 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadEngine.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadEngine.cs @@ -2,41 +2,40 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Bulkhead -{ +namespace Polly.Bulkhead; + internal static class AsyncBulkheadEngine +{ + internal static async Task ImplementationAsync( + Func> action, + Context context, + Func onBulkheadRejectedAsync, + SemaphoreSlim maxParallelizationSemaphore, + SemaphoreSlim maxQueuedActionsSemaphore, + CancellationToken cancellationToken, + bool continueOnCapturedContext) { - internal static async Task ImplementationAsync( - Func> action, - Context context, - Func onBulkheadRejectedAsync, - SemaphoreSlim maxParallelizationSemaphore, - SemaphoreSlim maxQueuedActionsSemaphore, - CancellationToken cancellationToken, - bool continueOnCapturedContext) + if (!await maxQueuedActionsSemaphore.WaitAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(continueOnCapturedContext)) { - if (!await maxQueuedActionsSemaphore.WaitAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(continueOnCapturedContext)) - { - await onBulkheadRejectedAsync(context).ConfigureAwait(continueOnCapturedContext); - throw new BulkheadRejectedException(); - } - try - { - await maxParallelizationSemaphore.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext); + await onBulkheadRejectedAsync(context).ConfigureAwait(continueOnCapturedContext); + throw new BulkheadRejectedException(); + } + try + { + await maxParallelizationSemaphore.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext); - try - { - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } - finally - { - maxParallelizationSemaphore.Release(); - } + try + { + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); } finally { - maxQueuedActionsSemaphore.Release(); + maxParallelizationSemaphore.Release(); } } + finally + { + maxQueuedActionsSemaphore.Release(); + } } } diff --git a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs b/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs index d5b445024ee..39b7ec8126d 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs @@ -3,99 +3,98 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Bulkhead -{ - /// - /// A bulkhead-isolation policy which can be applied to delegates. - /// - public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy - { - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private Func _onBulkheadRejectedAsync; +namespace Polly.Bulkhead; - internal AsyncBulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Func onBulkheadRejectedAsync) - { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); +/// +/// A bulkhead-isolation policy which can be applied to delegates. +/// +public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy +{ + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private Func _onBulkheadRejectedAsync; - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } + internal AsyncBulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Func onBulkheadRejectedAsync) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. - /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); - } + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); - /// - public void Dispose() - { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); - } + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); } - /// - /// A bulkhead-isolation policy which can be applied to delegates. - /// - /// The return type of delegates which may be executed through the policy. - public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy + /// + public void Dispose() { - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private Func _onBulkheadRejectedAsync; + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); + } +} - internal AsyncBulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Func onBulkheadRejectedAsync) - { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); +/// +/// A bulkhead-isolation policy which can be applied to delegates. +/// +/// The return type of delegates which may be executed through the policy. +public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy +{ + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private Func _onBulkheadRejectedAsync; - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } + internal AsyncBulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Func onBulkheadRejectedAsync) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); - } + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. - /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + } - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); - /// - public void Dispose() - { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); - } + /// + public void Dispose() + { + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs b/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs index d51fad7644c..44a93be2557 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs @@ -3,75 +3,74 @@ using System; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) - { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, 0, doNothingAsync); - } + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, 0, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejectedAsync - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) - => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejectedAsync + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) + => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) - { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) + { + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejectedAsync - public static AsyncBulkheadPolicy BulkheadAsync( - int maxParallelization, - int maxQueuingActions, - Func onBulkheadRejectedAsync) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejectedAsync + public static AsyncBulkheadPolicy BulkheadAsync( + int maxParallelization, + int maxQueuingActions, + Func onBulkheadRejectedAsync) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - return new AsyncBulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejectedAsync - ); - } + return new AsyncBulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejectedAsync + ); } } diff --git a/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs b/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs index 8f43e849fd5..f082cef5695 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs @@ -3,72 +3,71 @@ using System; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) - { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, 0, doNothingAsync); - } + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, 0, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejectedAsync - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) - => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejectedAsync + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) + => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejectedAsync - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) - { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejectedAsync + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) + { + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejectedAsync - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions, Func onBulkheadRejectedAsync) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejectedAsync + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions, Func onBulkheadRejectedAsync) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - return new AsyncBulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejectedAsync - ); - } + return new AsyncBulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejectedAsync + ); } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/BulkheadEngine.cs b/src/Polly/Bulkhead/BulkheadEngine.cs index 2d022848b46..6a9827fdd72 100644 --- a/src/Polly/Bulkhead/BulkheadEngine.cs +++ b/src/Polly/Bulkhead/BulkheadEngine.cs @@ -1,40 +1,39 @@ using System; using System.Threading; -namespace Polly.Bulkhead +namespace Polly.Bulkhead; + +internal static class BulkheadEngine { - internal static class BulkheadEngine + internal static TResult Implementation( + Func action, + Context context, + Action onBulkheadRejected, + SemaphoreSlim maxParallelizationSemaphore, + SemaphoreSlim maxQueuedActionsSemaphore, + CancellationToken cancellationToken) { - internal static TResult Implementation( - Func action, - Context context, - Action onBulkheadRejected, - SemaphoreSlim maxParallelizationSemaphore, - SemaphoreSlim maxQueuedActionsSemaphore, - CancellationToken cancellationToken) + if (!maxQueuedActionsSemaphore.Wait(TimeSpan.Zero, cancellationToken)) { - if (!maxQueuedActionsSemaphore.Wait(TimeSpan.Zero, cancellationToken)) - { - onBulkheadRejected(context); - throw new BulkheadRejectedException(); - } - + onBulkheadRejected(context); + throw new BulkheadRejectedException(); + } + + try + { + maxParallelizationSemaphore.Wait(cancellationToken); try { - maxParallelizationSemaphore.Wait(cancellationToken); - try - { - return action(context, cancellationToken); - } - finally - { - maxParallelizationSemaphore.Release(); - } + return action(context, cancellationToken); } finally { - maxQueuedActionsSemaphore.Release(); + maxParallelizationSemaphore.Release(); } } + finally + { + maxQueuedActionsSemaphore.Release(); + } } } diff --git a/src/Polly/Bulkhead/BulkheadPolicy.cs b/src/Polly/Bulkhead/BulkheadPolicy.cs index dd8125cabd7..7f7fa27e610 100644 --- a/src/Polly/Bulkhead/BulkheadPolicy.cs +++ b/src/Polly/Bulkhead/BulkheadPolicy.cs @@ -2,100 +2,99 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Bulkhead -{ - /// - /// A bulkhead-isolation policy which can be applied to delegates. - /// - public class BulkheadPolicy : Policy, IBulkheadPolicy - { - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private readonly Action _onBulkheadRejected; +namespace Polly.Bulkhead; - internal BulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Action onBulkheadRejected) - { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); +/// +/// A bulkhead-isolation policy which can be applied to delegates. +/// +public class BulkheadPolicy : Policy, IBulkheadPolicy +{ + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private readonly Action _onBulkheadRejected; - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } + internal BulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Action onBulkheadRejected) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. - /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; - /// - /// Disposes of the , allowing it to dispose its internal resources. - /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. - /// - public void Dispose() - { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); - } - } + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); /// - /// A bulkhead-isolation policy which can be applied to delegates returning a value of type . + /// Disposes of the , allowing it to dispose its internal resources. + /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. /// - public class BulkheadPolicy : Policy, IBulkheadPolicy + public void Dispose() { - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private readonly Action _onBulkheadRejected; + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); + } +} + +/// +/// A bulkhead-isolation policy which can be applied to delegates returning a value of type . +/// +public class BulkheadPolicy : Policy, IBulkheadPolicy +{ + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private readonly Action _onBulkheadRejected; - /// - internal BulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Action onBulkheadRejected) - { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); + /// + internal BulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Action onBulkheadRejected) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. - /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); - /// - /// Disposes of the , allowing it to dispose its internal resources. - /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. - /// - public void Dispose() - { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); - } + /// + /// Disposes of the , allowing it to dispose its internal resources. + /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. + /// + public void Dispose() + { + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); } } diff --git a/src/Polly/Bulkhead/BulkheadRejectedException.cs b/src/Polly/Bulkhead/BulkheadRejectedException.cs index 82898dbfdb3..bc41a539580 100644 --- a/src/Polly/Bulkhead/BulkheadRejectedException.cs +++ b/src/Polly/Bulkhead/BulkheadRejectedException.cs @@ -3,49 +3,48 @@ using System.Runtime.Serialization; #endif -namespace Polly.Bulkhead +namespace Polly.Bulkhead; + +/// +/// Exception thrown when a bulkhead's semaphore and queue are full. +/// +#if NETSTANDARD2_0 +[Serializable] +#endif +public class BulkheadRejectedException : ExecutionRejectedException { /// - /// Exception thrown when a bulkhead's semaphore and queue are full. + /// Initializes a new instance of the class. /// -#if NETSTANDARD2_0 - [Serializable] -#endif - public class BulkheadRejectedException : ExecutionRejectedException + public BulkheadRejectedException() : this("The bulkhead semaphore and queue are full and execution was rejected.") { - /// - /// Initializes a new instance of the class. - /// - public BulkheadRejectedException() : this("The bulkhead semaphore and queue are full and execution was rejected.") - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message. - public BulkheadRejectedException(string message) : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message. + public BulkheadRejectedException(string message) : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The inner exception. - public BulkheadRejectedException(string message, Exception innerException) : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The inner exception. + public BulkheadRejectedException(string message, Exception innerException) : base(message, innerException) + { + } #if NETSTANDARD2_0 - /// - /// Initializes a new instance of the class. - /// - /// The information. - /// The context. - protected BulkheadRejectedException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// + /// The information. + /// The context. + protected BulkheadRejectedException(SerializationInfo info, StreamingContext context) : base(info, context) + { } +#endif } diff --git a/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs b/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs index d9c3cf57815..a217948f843 100644 --- a/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs +++ b/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs @@ -1,19 +1,18 @@ using System.Threading; -namespace Polly.Bulkhead +namespace Polly.Bulkhead; + +internal static class BulkheadSemaphoreFactory { - internal static class BulkheadSemaphoreFactory + public static (SemaphoreSlim, SemaphoreSlim) CreateBulkheadSemaphores(int maxParallelization, int maxQueueingActions) { - public static (SemaphoreSlim, SemaphoreSlim) CreateBulkheadSemaphores(int maxParallelization, int maxQueueingActions) - { - var maxParallelizationSemaphore = new SemaphoreSlim(maxParallelization, maxParallelization); + var maxParallelizationSemaphore = new SemaphoreSlim(maxParallelization, maxParallelization); - var maxQueuingCompounded = maxQueueingActions <= int.MaxValue - maxParallelization - ? maxQueueingActions + maxParallelization - : int.MaxValue; - var maxQueuedActionsSemaphore = new SemaphoreSlim(maxQueuingCompounded, maxQueuingCompounded); + var maxQueuingCompounded = maxQueueingActions <= int.MaxValue - maxParallelization + ? maxQueueingActions + maxParallelization + : int.MaxValue; + var maxQueuedActionsSemaphore = new SemaphoreSlim(maxQueuingCompounded, maxQueuingCompounded); - return (maxParallelizationSemaphore, maxQueuedActionsSemaphore); - } + return (maxParallelizationSemaphore, maxQueuedActionsSemaphore); } } diff --git a/src/Polly/Bulkhead/BulkheadSyntax.cs b/src/Polly/Bulkhead/BulkheadSyntax.cs index 8c3a0caa3a8..e166f0db2df 100644 --- a/src/Polly/Bulkhead/BulkheadSyntax.cs +++ b/src/Polly/Bulkhead/BulkheadSyntax.cs @@ -1,73 +1,72 @@ using Polly.Bulkhead; using System; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// maxParallelization;Value must be greater than zero. + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization) { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// maxParallelization;Value must be greater than zero. - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization) - { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, 0, doNothing); - } + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, 0, doNothing); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejected - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) - => Bulkhead(maxParallelization, 0, onBulkheadRejected); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejected + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) + => Bulkhead(maxParallelization, 0, onBulkheadRejected); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) - { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, maxQueuingActions, doNothing); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) + { + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, maxQueuingActions, doNothing); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejected - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejected + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); - return new BulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejected - ); - } - + return new BulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejected + ); } + } diff --git a/src/Polly/Bulkhead/BulkheadTResultSyntax.cs b/src/Polly/Bulkhead/BulkheadTResultSyntax.cs index df7d595e655..ab6afc1d49e 100644 --- a/src/Polly/Bulkhead/BulkheadTResultSyntax.cs +++ b/src/Polly/Bulkhead/BulkheadTResultSyntax.cs @@ -1,73 +1,72 @@ using Polly.Bulkhead; using System; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// maxParallelization;Value must be greater than zero. + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization) { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// maxParallelization;Value must be greater than zero. - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization) - { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, 0, doNothing); - } - - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejected - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) - => Bulkhead(maxParallelization, 0, onBulkheadRejected); + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, 0, doNothing); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) - { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, maxQueuingActions, doNothing); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejected + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) + => Bulkhead(maxParallelization, 0, onBulkheadRejected); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejected - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) + { + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, maxQueuingActions, doNothing); + } - return new BulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejected - ); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejected + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); + return new BulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejected + ); } + } diff --git a/src/Polly/Bulkhead/IBulkheadPolicy.cs b/src/Polly/Bulkhead/IBulkheadPolicy.cs index 64273314a2e..7a48a593b6e 100644 --- a/src/Polly/Bulkhead/IBulkheadPolicy.cs +++ b/src/Polly/Bulkhead/IBulkheadPolicy.cs @@ -1,29 +1,28 @@ using System; -namespace Polly.Bulkhead +namespace Polly.Bulkhead; + +/// +/// Defines properties and methods common to all bulkhead policies. +/// + +public interface IBulkheadPolicy : IsPolicy, IDisposable { /// - /// Defines properties and methods common to all bulkhead policies. + /// Gets the number of slots currently available for executing actions through the bulkhead. /// - - public interface IBulkheadPolicy : IsPolicy, IDisposable - { - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. - /// - int BulkheadAvailableCount { get; } - - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - int QueueAvailableCount { get; } - } + int BulkheadAvailableCount { get; } /// - /// Defines properties and methods common to all bulkhead policies generic-typed for executions returning results of type . + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. /// - public interface IBulkheadPolicy : IBulkheadPolicy - { - - } + int QueueAvailableCount { get; } +} + +/// +/// Defines properties and methods common to all bulkhead policies generic-typed for executions returning results of type . +/// +public interface IBulkheadPolicy : IBulkheadPolicy +{ + } diff --git a/src/Polly/Caching/AbsoluteTtl.cs b/src/Polly/Caching/AbsoluteTtl.cs index 06e8c831495..e70c412c110 100644 --- a/src/Polly/Caching/AbsoluteTtl.cs +++ b/src/Polly/Caching/AbsoluteTtl.cs @@ -1,16 +1,15 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines a ttl strategy which will cache items until the specified point-in-time. +/// +public class AbsoluteTtl : NonSlidingTtl { /// - /// Defines a ttl strategy which will cache items until the specified point-in-time. + /// Initializes a new instance of the class. /// - public class AbsoluteTtl : NonSlidingTtl - { - /// - /// Initializes a new instance of the class. - /// - /// The UTC point in time until which to consider the cache item valid. - public AbsoluteTtl(DateTimeOffset absoluteExpirationTime) : base(absoluteExpirationTime) { } - } + /// The UTC point in time until which to consider the cache item valid. + public AbsoluteTtl(DateTimeOffset absoluteExpirationTime) : base(absoluteExpirationTime) { } } diff --git a/src/Polly/Caching/AsyncCacheEngine.cs b/src/Polly/Caching/AsyncCacheEngine.cs index 2bab229bf4d..21fa07ac4e2 100644 --- a/src/Polly/Caching/AsyncCacheEngine.cs +++ b/src/Polly/Caching/AsyncCacheEngine.cs @@ -2,71 +2,70 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching +namespace Polly.Caching; + +internal static class AsyncCacheEngine { - internal static class AsyncCacheEngine + internal static async Task ImplementationAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) { - internal static async Task ImplementationAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) + cancellationToken.ThrowIfCancellationRequested(); + + string cacheKey = cacheKeyStrategy(context); + if (cacheKey == null) { - cancellationToken.ThrowIfCancellationRequested(); + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } - string cacheKey = cacheKeyStrategy(context); - if (cacheKey == null) - { - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + bool cacheHit; + TResult valueFromCache; + try + { + (cacheHit, valueFromCache) = await cacheProvider.TryGetAsync(cacheKey, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + } + catch (Exception ex) + { + cacheHit = false; + valueFromCache = default; + onCacheGetError(context, cacheKey, ex); + } + if (cacheHit) + { + onCacheGet(context, cacheKey); + return valueFromCache; + } + else + { + onCacheMiss(context, cacheKey); + } - bool cacheHit; - TResult valueFromCache; + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + + Ttl ttl = ttlStrategy.GetTtl(context, result); + if (ttl.Timespan > TimeSpan.Zero) + { try { - (cacheHit, valueFromCache) = await cacheProvider.TryGetAsync(cacheKey, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + await cacheProvider.PutAsync(cacheKey, result, ttl, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + onCachePut(context, cacheKey); } catch (Exception ex) { - cacheHit = false; - valueFromCache = default; - onCacheGetError(context, cacheKey, ex); - } - if (cacheHit) - { - onCacheGet(context, cacheKey); - return valueFromCache; - } - else - { - onCacheMiss(context, cacheKey); + onCachePutError(context, cacheKey, ex); } - - TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - - Ttl ttl = ttlStrategy.GetTtl(context, result); - if (ttl.Timespan > TimeSpan.Zero) - { - try - { - await cacheProvider.PutAsync(cacheKey, result, ttl, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - onCachePut(context, cacheKey); - } - catch (Exception ex) - { - onCachePutError(context, cacheKey, ex); - } - } - - return result; } + + return result; } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/AsyncCachePolicy.cs b/src/Polly/Caching/AsyncCachePolicy.cs index 903c2bfdacb..e314f98d955 100644 --- a/src/Polly/Caching/AsyncCachePolicy.cs +++ b/src/Polly/Caching/AsyncCachePolicy.cs @@ -3,132 +3,131 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching -{ - /// - /// A cache policy that can be applied to the results of delegate executions. - /// - public class AsyncCachePolicy : AsyncPolicy - { - private readonly IAsyncCacheProvider _asyncCacheProvider; - private readonly ITtlStrategy _ttlStrategy; - private readonly Func _cacheKeyStrategy; +namespace Polly.Caching; - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; +/// +/// A cache policy that can be applied to the results of delegate executions. +/// +public class AsyncCachePolicy : AsyncPolicy +{ + private readonly IAsyncCacheProvider _asyncCacheProvider; + private readonly ITtlStrategy _ttlStrategy; + private readonly Func _cacheKeyStrategy; - internal AsyncCachePolicy( - IAsyncCacheProvider asyncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _asyncCacheProvider = asyncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; - } + internal AsyncCachePolicy( + IAsyncCacheProvider asyncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _asyncCacheProvider = asyncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - /// - protected override Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - // Pass-through/NOOP policy action, for void-returning executions through the cache policy. - return action(context, cancellationToken); - } + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; + } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncCacheEngine.ImplementationAsync( - _asyncCacheProvider.AsyncFor(), - _ttlStrategy.For(), - _cacheKeyStrategy, - action, - context, - cancellationToken, - continueOnCapturedContext, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); - } + /// + protected override Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + // Pass-through/NOOP policy action, for void-returning executions through the cache policy. + return action(context, cancellationToken); } - /// - /// A cache policy that can be applied to the results of delegate executions. - /// - /// The return type of delegates which may be executed through the policy. - public class AsyncCachePolicy : AsyncPolicy + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) { - private IAsyncCacheProvider _asyncCacheProvider; - private readonly ITtlStrategy _ttlStrategy; - private readonly Func _cacheKeyStrategy; + return AsyncCacheEngine.ImplementationAsync( + _asyncCacheProvider.AsyncFor(), + _ttlStrategy.For(), + _cacheKeyStrategy, + action, + context, + cancellationToken, + continueOnCapturedContext, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); + } +} - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; +/// +/// A cache policy that can be applied to the results of delegate executions. +/// +/// The return type of delegates which may be executed through the policy. +public class AsyncCachePolicy : AsyncPolicy +{ + private IAsyncCacheProvider _asyncCacheProvider; + private readonly ITtlStrategy _ttlStrategy; + private readonly Func _cacheKeyStrategy; - internal AsyncCachePolicy( - IAsyncCacheProvider asyncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _asyncCacheProvider = asyncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; - } + internal AsyncCachePolicy( + IAsyncCacheProvider asyncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _asyncCacheProvider = asyncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncCacheEngine.ImplementationAsync( - _asyncCacheProvider, - _ttlStrategy, - _cacheKeyStrategy, - action, - context, - cancellationToken, - continueOnCapturedContext, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); - } + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; } + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncCacheEngine.ImplementationAsync( + _asyncCacheProvider, + _ttlStrategy, + _cacheKeyStrategy, + action, + context, + cancellationToken, + continueOnCapturedContext, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); + } } + diff --git a/src/Polly/Caching/AsyncCacheSyntax.cs b/src/Polly/Caching/AsyncCacheSyntax.cs index 2579215b4b9..1525083c55e 100644 --- a/src/Polly/Caching/AsyncCacheSyntax.cs +++ b/src/Polly/Caching/AsyncCacheSyntax.cs @@ -1,328 +1,327 @@ using Polly.Caching; using System; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy - { - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); } } diff --git a/src/Polly/Caching/AsyncCacheTResultSyntax.cs b/src/Polly/Caching/AsyncCacheTResultSyntax.cs index 89e32f88c66..3604a9a7dc5 100644 --- a/src/Polly/Caching/AsyncCacheTResultSyntax.cs +++ b/src/Polly/Caching/AsyncCacheTResultSyntax.cs @@ -1,823 +1,822 @@ using Polly.Caching; using System; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) { - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy.For(), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy.For(), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); } } diff --git a/src/Polly/Caching/AsyncGenericCacheProvider.cs b/src/Polly/Caching/AsyncGenericCacheProvider.cs index a0a476400de..c4432d5c662 100644 --- a/src/Polly/Caching/AsyncGenericCacheProvider.cs +++ b/src/Polly/Caching/AsyncGenericCacheProvider.cs @@ -2,26 +2,25 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching -{ - /// - /// Provides a strongly-typed wrapper over a non-generic CacheProviderAsync. - /// - /// The type of the objects in the cache. - internal class AsyncGenericCacheProvider : IAsyncCacheProvider - { - private readonly IAsyncCacheProvider _wrappedCacheProvider; +namespace Polly.Caching; - internal AsyncGenericCacheProvider(IAsyncCacheProvider nonGenericCacheProvider) - => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); +/// +/// Provides a strongly-typed wrapper over a non-generic CacheProviderAsync. +/// +/// The type of the objects in the cache. +internal class AsyncGenericCacheProvider : IAsyncCacheProvider +{ + private readonly IAsyncCacheProvider _wrappedCacheProvider; - async Task<(bool, TCacheFormat)> IAsyncCacheProvider.TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - (bool cacheHit, object result) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return (cacheHit, (TCacheFormat)(result ?? default(TCacheFormat))); - } + internal AsyncGenericCacheProvider(IAsyncCacheProvider nonGenericCacheProvider) + => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); - Task IAsyncCacheProvider.PutAsync(string key, TCacheFormat value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - => _wrappedCacheProvider.PutAsync(key, value, ttl, cancellationToken, continueOnCapturedContext); + async Task<(bool, TCacheFormat)> IAsyncCacheProvider.TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + (bool cacheHit, object result) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (cacheHit, (TCacheFormat)(result ?? default(TCacheFormat))); } + + Task IAsyncCacheProvider.PutAsync(string key, TCacheFormat value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + => _wrappedCacheProvider.PutAsync(key, value, ttl, cancellationToken, continueOnCapturedContext); } diff --git a/src/Polly/Caching/AsyncSerializingCacheProvider.cs b/src/Polly/Caching/AsyncSerializingCacheProvider.cs index c4c8a17a0ea..e5dfe5e6683 100644 --- a/src/Polly/Caching/AsyncSerializingCacheProvider.cs +++ b/src/Polly/Caching/AsyncSerializingCacheProvider.cs @@ -2,126 +2,125 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with asynchronous . +/// +/// The type of serialized objects to be placed in the cache. +public class AsyncSerializingCacheProvider : IAsyncCacheProvider { + private readonly IAsyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; + /// - /// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with asynchronous . + /// Initializes a new instance of the class. /// - /// The type of serialized objects to be placed in the cache. - public class AsyncSerializingCacheProvider : IAsyncCacheProvider + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - private readonly IAsyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; - - /// - /// Initializes a new instance of the class. - /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) - { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - } - - /// - /// Gets a value from the cache asynchronously. - /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (null if not found). - /// - public async Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - (bool cacheHit, TSerialized objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); - } + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// A which completes when the value has been cached. - public async Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - await _wrappedCacheProvider.PutAsync( - key, - _serializer.Serialize(value), - ttl, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); - } + /// + /// Gets a value from the cache asynchronously. + /// + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (null if not found). + /// + public async Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + (bool cacheHit, TSerialized objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); } /// - /// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with asynchronous . + /// Puts the specified value in the cache asynchronously. /// - /// The return type of delegates which may be executed through the policy. - /// The type of serialized objects to be placed in the cache. - public class AsyncSerializingCacheProvider : IAsyncCacheProvider + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// A which completes when the value has been cached. + public async Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, + bool continueOnCapturedContext) { - private readonly IAsyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; + await _wrappedCacheProvider.PutAsync( + key, + _serializer.Serialize(value), + ttl, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); + } +} - /// - /// Initializes a new instance of the class. - /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) - { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - } +/// +/// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with asynchronous . +/// +/// The return type of delegates which may be executed through the policy. +/// The type of serialized objects to be placed in the cache. +public class AsyncSerializingCacheProvider : IAsyncCacheProvider +{ + private readonly IAsyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; - /// - /// Gets a value from the cache asynchronously. - /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). - /// - public async Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - (bool cacheHit, TSerialized objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); - } + /// + /// Initializes a new instance of the class. + /// + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) + { + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// A which completes when the value has been cached. - public async Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - await _wrappedCacheProvider.PutAsync( - key, - _serializer.Serialize(value), - ttl, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); - } + /// + /// Gets a value from the cache asynchronously. + /// + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). + /// + public async Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + (bool cacheHit, TSerialized objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); + } + + /// + /// Puts the specified value in the cache asynchronously. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// A which completes when the value has been cached. + public async Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + await _wrappedCacheProvider.PutAsync( + key, + _serializer.Serialize(value), + ttl, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); } } diff --git a/src/Polly/Caching/CacheEngine.cs b/src/Polly/Caching/CacheEngine.cs index 192841e7eb5..cc1c1be654d 100644 --- a/src/Polly/Caching/CacheEngine.cs +++ b/src/Polly/Caching/CacheEngine.cs @@ -1,70 +1,69 @@ using System; using System.Threading; -namespace Polly.Caching +namespace Polly.Caching; + +internal static class CacheEngine { - internal static class CacheEngine + internal static TResult Implementation( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Func action, + Context context, + CancellationToken cancellationToken, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) { - internal static TResult Implementation( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Func action, - Context context, - CancellationToken cancellationToken, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) + cancellationToken.ThrowIfCancellationRequested(); + + string cacheKey = cacheKeyStrategy(context); + if (cacheKey == null) { - cancellationToken.ThrowIfCancellationRequested(); + return action(context, cancellationToken); + } - string cacheKey = cacheKeyStrategy(context); - if (cacheKey == null) - { - return action(context, cancellationToken); - } + bool cacheHit; + TResult valueFromCache; + try + { + (cacheHit, valueFromCache) = cacheProvider.TryGet(cacheKey); + } + catch (Exception ex) + { + cacheHit = false; + valueFromCache = default; + onCacheGetError(context, cacheKey, ex); + } + if (cacheHit) + { + onCacheGet(context, cacheKey); + return valueFromCache; + } + else + { + onCacheMiss(context, cacheKey); + } - bool cacheHit; - TResult valueFromCache; + TResult result = action(context, cancellationToken); + + Ttl ttl = ttlStrategy.GetTtl(context, result); + if (ttl.Timespan > TimeSpan.Zero) + { try { - (cacheHit, valueFromCache) = cacheProvider.TryGet(cacheKey); + cacheProvider.Put(cacheKey, result, ttl); + onCachePut(context, cacheKey); } catch (Exception ex) { - cacheHit = false; - valueFromCache = default; - onCacheGetError(context, cacheKey, ex); - } - if (cacheHit) - { - onCacheGet(context, cacheKey); - return valueFromCache; - } - else - { - onCacheMiss(context, cacheKey); + onCachePutError(context, cacheKey, ex); } - - TResult result = action(context, cancellationToken); - - Ttl ttl = ttlStrategy.GetTtl(context, result); - if (ttl.Timespan > TimeSpan.Zero) - { - try - { - cacheProvider.Put(cacheKey, result, ttl); - onCachePut(context, cacheKey); - } - catch (Exception ex) - { - onCachePutError(context, cacheKey, ex); - } - } - - return result; } + + return result; } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/CachePolicy.cs b/src/Polly/Caching/CachePolicy.cs index 1c63070bc23..f6a82a2e2ad 100644 --- a/src/Polly/Caching/CachePolicy.cs +++ b/src/Polly/Caching/CachePolicy.cs @@ -2,118 +2,117 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Caching -{ - /// - /// A cache policy that can be applied to the results of delegate executions. - /// - public class CachePolicy : Policy, ICachePolicy - { - private readonly ISyncCacheProvider _syncCacheProvider; - private readonly ITtlStrategy _ttlStrategy; - private readonly Func _cacheKeyStrategy; - - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; +namespace Polly.Caching; - internal CachePolicy( - ISyncCacheProvider syncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _syncCacheProvider = syncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; +/// +/// A cache policy that can be applied to the results of delegate executions. +/// +public class CachePolicy : Policy, ICachePolicy +{ + private readonly ISyncCacheProvider _syncCacheProvider; + private readonly ITtlStrategy _ttlStrategy; + private readonly Func _cacheKeyStrategy; - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; - } + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - /// - protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) - // Pass-through/NOOP policy action, for void-returning calls through a cache policy. - => action(context, cancellationToken); + internal CachePolicy( + ISyncCacheProvider syncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _syncCacheProvider = syncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - return CacheEngine.Implementation( - _syncCacheProvider.For(), - _ttlStrategy.For(), - _cacheKeyStrategy, - action, - context, - cancellationToken, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); - } + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; } - /// - /// A cache policy that can be applied to the results of delegate executions. - /// - public class CachePolicy : Policy, ICachePolicy + /// + protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) + // Pass-through/NOOP policy action, for void-returning calls through a cache policy. + => action(context, cancellationToken); + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) { - private ISyncCacheProvider _syncCacheProvider; - private ITtlStrategy _ttlStrategy; - private Func _cacheKeyStrategy; + return CacheEngine.Implementation( + _syncCacheProvider.For(), + _ttlStrategy.For(), + _cacheKeyStrategy, + action, + context, + cancellationToken, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); + } +} - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; +/// +/// A cache policy that can be applied to the results of delegate executions. +/// +public class CachePolicy : Policy, ICachePolicy +{ + private ISyncCacheProvider _syncCacheProvider; + private ITtlStrategy _ttlStrategy; + private Func _cacheKeyStrategy; - internal CachePolicy( - ISyncCacheProvider syncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _syncCacheProvider = syncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; - } + internal CachePolicy( + ISyncCacheProvider syncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _syncCacheProvider = syncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => CacheEngine.Implementation( - _syncCacheProvider, - _ttlStrategy, - _cacheKeyStrategy, - action, - context, - cancellationToken, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; } + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => CacheEngine.Implementation( + _syncCacheProvider, + _ttlStrategy, + _cacheKeyStrategy, + action, + context, + cancellationToken, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); } diff --git a/src/Polly/Caching/CacheProviderExtensions.cs b/src/Polly/Caching/CacheProviderExtensions.cs index 3cfef047503..3706e2a2abc 100644 --- a/src/Polly/Caching/CacheProviderExtensions.cs +++ b/src/Polly/Caching/CacheProviderExtensions.cs @@ -1,72 +1,71 @@ -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Class that provides helper methods for configuring CacheProviders. +/// +public static class CacheProviderExtensions { /// - /// Class that provides helper methods for configuring CacheProviders. + /// Provides a strongly -typed version of the supplied /// - public static class CacheProviderExtensions - { - /// - /// Provides a strongly -typed version of the supplied - /// - /// The type the returned will handle. - /// The non-generic cache provider to wrap. - /// ISyncCacheProvider{TCacheFormat}. - public static ISyncCacheProvider For(this ISyncCacheProvider nonGenericCacheProvider) - => new GenericCacheProvider(nonGenericCacheProvider); + /// The type the returned will handle. + /// The non-generic cache provider to wrap. + /// ISyncCacheProvider{TCacheFormat}. + public static ISyncCacheProvider For(this ISyncCacheProvider nonGenericCacheProvider) + => new GenericCacheProvider(nonGenericCacheProvider); - /// - /// Provides a strongly -typed version of the supplied - /// - /// The type the returned will handle. - /// The non-generic cache provider to wrap. - /// IAsyncCacheProvider{TCacheFormat}. - public static IAsyncCacheProvider AsyncFor(this IAsyncCacheProvider nonGenericCacheProvider) - => new AsyncGenericCacheProvider(nonGenericCacheProvider); + /// + /// Provides a strongly -typed version of the supplied + /// + /// The type the returned will handle. + /// The non-generic cache provider to wrap. + /// IAsyncCacheProvider{TCacheFormat}. + public static IAsyncCacheProvider AsyncFor(this IAsyncCacheProvider nonGenericCacheProvider) + => new AsyncGenericCacheProvider(nonGenericCacheProvider); - /// - /// Wraps the around the so that delegate return values of any type can be stored in the cache as type . - /// - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// A serializer which can serialize/deserialize all types to/from . - /// SerializingCacheProvider<TResult, TSerialized>. - public static SerializingCacheProvider WithSerializer( - this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new SerializingCacheProvider(cacheProvider, serializer); + /// + /// Wraps the around the so that delegate return values of any type can be stored in the cache as type . + /// + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// A serializer which can serialize/deserialize all types to/from . + /// SerializingCacheProvider<TResult, TSerialized>. + public static SerializingCacheProvider WithSerializer( + this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new SerializingCacheProvider(cacheProvider, serializer); - /// - /// Wraps the around the so that delegate return values of type can be stored in the cache as type . - /// - /// The return type of delegates which may be executed through the policy. - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// The serializer. - /// SerializingCacheProvider<TResult, TSerialized>. - public static SerializingCacheProvider WithSerializer( - this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new SerializingCacheProvider(cacheProvider, serializer); + /// + /// Wraps the around the so that delegate return values of type can be stored in the cache as type . + /// + /// The return type of delegates which may be executed through the policy. + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// The serializer. + /// SerializingCacheProvider<TResult, TSerialized>. + public static SerializingCacheProvider WithSerializer( + this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new SerializingCacheProvider(cacheProvider, serializer); - /// - /// Wraps the around the asynchronous so that delegate return values of any type can be stored in the cache as type . - /// - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// A serializer which can serialize/deserialize all types to/from . - /// SerializingCacheProvider<TResult, TSerialized>. - public static AsyncSerializingCacheProvider WithSerializer( - this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new AsyncSerializingCacheProvider(cacheProvider, serializer); + /// + /// Wraps the around the asynchronous so that delegate return values of any type can be stored in the cache as type . + /// + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// A serializer which can serialize/deserialize all types to/from . + /// SerializingCacheProvider<TResult, TSerialized>. + public static AsyncSerializingCacheProvider WithSerializer( + this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new AsyncSerializingCacheProvider(cacheProvider, serializer); - /// - /// Wraps the around the asynchronous so that delegate return values of type can be stored in the cache as type . - /// - /// The return type of delegates which may be executed through the policy. - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// The serializer. - /// SerializingCacheProvider<TResult, TSerialized>. - public static AsyncSerializingCacheProvider WithSerializer( - this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new AsyncSerializingCacheProvider(cacheProvider, serializer); - } + /// + /// Wraps the around the asynchronous so that delegate return values of type can be stored in the cache as type . + /// + /// The return type of delegates which may be executed through the policy. + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// The serializer. + /// SerializingCacheProvider<TResult, TSerialized>. + public static AsyncSerializingCacheProvider WithSerializer( + this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new AsyncSerializingCacheProvider(cacheProvider, serializer); } diff --git a/src/Polly/Caching/CacheSyntax.cs b/src/Polly/Caching/CacheSyntax.cs index 612dba40f34..a669b1e35b4 100644 --- a/src/Polly/Caching/CacheSyntax.cs +++ b/src/Polly/Caching/CacheSyntax.cs @@ -1,340 +1,339 @@ using Polly.Caching; using System; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy - { - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } + return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); } } diff --git a/src/Polly/Caching/CacheTResultSyntax.cs b/src/Polly/Caching/CacheTResultSyntax.cs index c291902bfab..fe09af2fea1 100644 --- a/src/Polly/Caching/CacheTResultSyntax.cs +++ b/src/Polly/Caching/CacheTResultSyntax.cs @@ -1,828 +1,827 @@ using Polly.Caching; using System; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) { - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, - emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, - onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, - emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, - onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - - return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, + emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, + onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, + emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, + onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCacheGetError == null) throw new ArgumentNullException(nameof(onCacheGetError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + + return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); } } diff --git a/src/Polly/Caching/ContextualTtl.cs b/src/Polly/Caching/ContextualTtl.cs index 186e0062053..e36e6d09d98 100644 --- a/src/Polly/Caching/ContextualTtl.cs +++ b/src/Polly/Caching/ContextualTtl.cs @@ -1,44 +1,43 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines a ttl strategy which will cache items for a TimeSpan which may be influenced by data in the execution context. +/// +public class ContextualTtl : ITtlStrategy { /// - /// Defines a ttl strategy which will cache items for a TimeSpan which may be influenced by data in the execution context. + /// The key on the execution to use for storing the Ttl TimeSpan for which to cache. /// - public class ContextualTtl : ITtlStrategy - { - /// - /// The key on the execution to use for storing the Ttl TimeSpan for which to cache. - /// - public static readonly string TimeSpanKey = "ContextualTtlTimeSpan"; - - /// - /// The key on the execution to use for storing whether the Ttl should be treated as sliding expiration. - /// If no value is provided for this key, a ttl will not be treated as sliding expiration. - /// - public static readonly string SlidingExpirationKey = "ContextualTtlSliding"; - - private static readonly Ttl _noTtl = new Ttl(TimeSpan.Zero, false); - - /// - /// Gets the TimeSpan for which to keep an item about to be cached, which may be influenced by data in the execution context. - /// - /// The execution context. - /// The execution result. - /// TimeSpan. - public Ttl GetTtl(Context context, object result) - { - if (!context.ContainsKey(TimeSpanKey)) return _noTtl; + public static readonly string TimeSpanKey = "ContextualTtlTimeSpan"; + + /// + /// The key on the execution to use for storing whether the Ttl should be treated as sliding expiration. + /// If no value is provided for this key, a ttl will not be treated as sliding expiration. + /// + public static readonly string SlidingExpirationKey = "ContextualTtlSliding"; - bool sliding = false; + private static readonly Ttl _noTtl = new Ttl(TimeSpan.Zero, false); - if (context.TryGetValue(SlidingExpirationKey, out object objValue)) - { - sliding = objValue as bool? ?? false; - } + /// + /// Gets the TimeSpan for which to keep an item about to be cached, which may be influenced by data in the execution context. + /// + /// The execution context. + /// The execution result. + /// TimeSpan. + public Ttl GetTtl(Context context, object result) + { + if (!context.ContainsKey(TimeSpanKey)) return _noTtl; - return new Ttl(context[TimeSpanKey] as TimeSpan? ?? TimeSpan.Zero, sliding); + bool sliding = false; + + if (context.TryGetValue(SlidingExpirationKey, out object objValue)) + { + sliding = objValue as bool? ?? false; } + return new Ttl(context[TimeSpanKey] as TimeSpan? ?? TimeSpan.Zero, sliding); } + } diff --git a/src/Polly/Caching/DefaultCacheKeyStrategy.cs b/src/Polly/Caching/DefaultCacheKeyStrategy.cs index 28a906c2a86..09a4ff88c85 100644 --- a/src/Polly/Caching/DefaultCacheKeyStrategy.cs +++ b/src/Polly/Caching/DefaultCacheKeyStrategy.cs @@ -1,20 +1,19 @@ -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// The default cache key strategy for . Returns the property . +/// +public class DefaultCacheKeyStrategy : ICacheKeyStrategy { /// - /// The default cache key strategy for . Returns the property . + /// Gets the cache key from the given execution context. /// - public class DefaultCacheKeyStrategy : ICacheKeyStrategy - { - /// - /// Gets the cache key from the given execution context. - /// - /// The execution context. - /// The cache key - public string GetCacheKey(Context context) => context.OperationKey; + /// The execution context. + /// The cache key + public string GetCacheKey(Context context) => context.OperationKey; - /// - /// Gets an instance of the . - /// - public static readonly ICacheKeyStrategy Instance = new DefaultCacheKeyStrategy(); - } + /// + /// Gets an instance of the . + /// + public static readonly ICacheKeyStrategy Instance = new DefaultCacheKeyStrategy(); } diff --git a/src/Polly/Caching/GenericCacheProvider.cs b/src/Polly/Caching/GenericCacheProvider.cs index 122cf09efdb..c4f1498668e 100644 --- a/src/Polly/Caching/GenericCacheProvider.cs +++ b/src/Polly/Caching/GenericCacheProvider.cs @@ -1,25 +1,24 @@ using System; -namespace Polly.Caching -{ - /// - /// Provides a strongly-typed wrapper over a non-generic CacheProvider. - /// - /// The type of the objects in the cache. - internal class GenericCacheProvider : ISyncCacheProvider - { - private readonly ISyncCacheProvider _wrappedCacheProvider; +namespace Polly.Caching; - internal GenericCacheProvider(ISyncCacheProvider nonGenericCacheProvider) - => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); +/// +/// Provides a strongly-typed wrapper over a non-generic CacheProvider. +/// +/// The type of the objects in the cache. +internal class GenericCacheProvider : ISyncCacheProvider +{ + private readonly ISyncCacheProvider _wrappedCacheProvider; - (bool, TCacheFormat) ISyncCacheProvider.TryGet(string key) - { - (bool cacheHit, object result) = _wrappedCacheProvider.TryGet(key); - return (cacheHit, (TCacheFormat) (result ?? default(TCacheFormat))); - } + internal GenericCacheProvider(ISyncCacheProvider nonGenericCacheProvider) + => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); - void ISyncCacheProvider.Put(string key, TCacheFormat value, Ttl ttl) - => _wrappedCacheProvider.Put(key, value, ttl); + (bool, TCacheFormat) ISyncCacheProvider.TryGet(string key) + { + (bool cacheHit, object result) = _wrappedCacheProvider.TryGet(key); + return (cacheHit, (TCacheFormat) (result ?? default(TCacheFormat))); } + + void ISyncCacheProvider.Put(string key, TCacheFormat value, Ttl ttl) + => _wrappedCacheProvider.Put(key, value, ttl); } diff --git a/src/Polly/Caching/GenericTtlStrategy.cs b/src/Polly/Caching/GenericTtlStrategy.cs index e5fba9579e0..d0929a207e3 100644 --- a/src/Polly/Caching/GenericTtlStrategy.cs +++ b/src/Polly/Caching/GenericTtlStrategy.cs @@ -1,23 +1,22 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Represents a strongly-typed wrapper of a non-generic strategy. +/// +internal class GenericTtlStrategy : ITtlStrategy { - /// - /// Represents a strongly-typed wrapper of a non-generic strategy. - /// - internal class GenericTtlStrategy : ITtlStrategy - { - private readonly ITtlStrategy _wrappedTtlStrategy; + private readonly ITtlStrategy _wrappedTtlStrategy; - internal GenericTtlStrategy(ITtlStrategy ttlStrategy) - => _wrappedTtlStrategy = ttlStrategy ?? throw new ArgumentNullException(nameof(ttlStrategy)); + internal GenericTtlStrategy(ITtlStrategy ttlStrategy) + => _wrappedTtlStrategy = ttlStrategy ?? throw new ArgumentNullException(nameof(ttlStrategy)); - /// - /// Gets a TTL for a cacheable item, given the current execution context and result. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, TResult result) => _wrappedTtlStrategy.GetTtl(context, result); - } + /// + /// Gets a TTL for a cacheable item, given the current execution context and result. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + public Ttl GetTtl(Context context, TResult result) => _wrappedTtlStrategy.GetTtl(context, result); } diff --git a/src/Polly/Caching/IAsyncCacheProvider.cs b/src/Polly/Caching/IAsyncCacheProvider.cs index 31b4622fe4d..be1c9df86d9 100644 --- a/src/Polly/Caching/IAsyncCacheProvider.cs +++ b/src/Polly/Caching/IAsyncCacheProvider.cs @@ -1,63 +1,62 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines methods for classes providing asynchronous cache functionality for Polly s. +/// +public interface IAsyncCacheProvider { /// - /// Defines methods for classes providing asynchronous cache functionality for Polly s. + /// Gets a value from the cache asynchronously. /// - public interface IAsyncCacheProvider - { - /// - /// Gets a value from the cache asynchronously. - /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (null if not found). - /// - Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (null if not found). + /// + Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// A which completes when the value has been cached. - Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); - } + /// + /// Puts the specified value in the cache asynchronously. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// A which completes when the value has been cached. + Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); +} +/// +/// Defines methods for classes providing asynchronous cache functionality for Polly s. +/// +public interface IAsyncCacheProvider +{ /// - /// Defines methods for classes providing asynchronous cache functionality for Polly s. + /// Gets a value from the cache asynchronously. /// - public interface IAsyncCacheProvider - { - /// - /// Gets a value from the cache asynchronously. - /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). - /// - Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). + /// + Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// A which completes when the value has been cached. - Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); - } -} \ No newline at end of file + /// + /// Puts the specified value in the cache asynchronously. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// A which completes when the value has been cached. + Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); +} diff --git a/src/Polly/Caching/ICacheItemSerializer.cs b/src/Polly/Caching/ICacheItemSerializer.cs index 3b8f39fef3b..1c36d393276 100644 --- a/src/Polly/Caching/ICacheItemSerializer.cs +++ b/src/Polly/Caching/ICacheItemSerializer.cs @@ -1,25 +1,24 @@ -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines operations for serializing and deserializing values being placed in caches by instances. +/// +/// The type of objects that this serializer can serialize. +/// The type of objects after serialization. +public interface ICacheItemSerializer { /// - /// Defines operations for serializing and deserializing values being placed in caches by instances. + /// Serializes the specified object. /// - /// The type of objects that this serializer can serialize. - /// The type of objects after serialization. - public interface ICacheItemSerializer - { - /// - /// Serializes the specified object. - /// - /// The object to serialize. - /// The serialized object - TSerialized Serialize(TResult objectToSerialize); + /// The object to serialize. + /// The serialized object + TSerialized Serialize(TResult objectToSerialize); - /// - /// Deserializes the specified object. - /// - /// The object to deserialize. - /// The deserialized object - TResult Deserialize(TSerialized objectToDeserialize); + /// + /// Deserializes the specified object. + /// + /// The object to deserialize. + /// The deserialized object + TResult Deserialize(TSerialized objectToDeserialize); - } } diff --git a/src/Polly/Caching/ICacheKeyStrategy.cs b/src/Polly/Caching/ICacheKeyStrategy.cs index bea3c94bee3..ad29dedcec9 100644 --- a/src/Polly/Caching/ICacheKeyStrategy.cs +++ b/src/Polly/Caching/ICacheKeyStrategy.cs @@ -1,15 +1,14 @@ -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines how a should get a string cache key from an execution +/// +public interface ICacheKeyStrategy { /// - /// Defines how a should get a string cache key from an execution + /// Gets the cache key from the given execution context. /// - public interface ICacheKeyStrategy - { - /// - /// Gets the cache key from the given execution context. - /// - /// The execution context. - /// The cache key - string GetCacheKey(Context context); - } + /// The execution context. + /// The cache key + string GetCacheKey(Context context); } diff --git a/src/Polly/Caching/ICachePolicy.cs b/src/Polly/Caching/ICachePolicy.cs index 304b8c0cf0b..3e9cff77b85 100644 --- a/src/Polly/Caching/ICachePolicy.cs +++ b/src/Polly/Caching/ICachePolicy.cs @@ -1,17 +1,16 @@ -namespace Polly.Caching -{ - /// - /// Defines properties and methods common to all Cache policies. - /// +namespace Polly.Caching; - public interface ICachePolicy : IsPolicy - { - } +/// +/// Defines properties and methods common to all Cache policies. +/// - /// - /// Defines properties and methods common to all Cache policies generic-typed for executions returning results of type . - /// - public interface ICachePolicy : ICachePolicy - { - } +public interface ICachePolicy : IsPolicy +{ +} + +/// +/// Defines properties and methods common to all Cache policies generic-typed for executions returning results of type . +/// +public interface ICachePolicy : ICachePolicy +{ } diff --git a/src/Polly/Caching/ISyncCacheProvider.cs b/src/Polly/Caching/ISyncCacheProvider.cs index 15390ee029d..17a6b0b1ba5 100644 --- a/src/Polly/Caching/ISyncCacheProvider.cs +++ b/src/Polly/Caching/ISyncCacheProvider.cs @@ -1,50 +1,49 @@ -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines methods for classes providing synchronous cache functionality for Polly s. +/// +public interface ISyncCacheProvider { /// - /// Defines methods for classes providing synchronous cache functionality for Polly s. + /// Gets a value from cache. /// - public interface ISyncCacheProvider - { - /// - /// Gets a value from cache. - /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (null if not found). - /// - (bool, object) TryGet(string key); + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (null if not found). + /// + (bool, object) TryGet(string key); - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - void Put(string key, object value, Ttl ttl); - } + /// + /// Puts the specified value in the cache. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + void Put(string key, object value, Ttl ttl); +} +/// +/// Defines methods for classes providing synchronous cache functionality for Polly s. +/// +public interface ISyncCacheProvider +{ /// - /// Defines methods for classes providing synchronous cache functionality for Polly s. + /// Gets a value from cache. /// - public interface ISyncCacheProvider - { - /// - /// Gets a value from cache. - /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (default(TResult) if not found). - /// - (bool, TResult) TryGet(string key); + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (default(TResult) if not found). + /// + (bool, TResult) TryGet(string key); - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - void Put(string key, TResult value, Ttl ttl); - } -} \ No newline at end of file + /// + /// Puts the specified value in the cache. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + void Put(string key, TResult value, Ttl ttl); +} diff --git a/src/Polly/Caching/ITtlStrategy.cs b/src/Polly/Caching/ITtlStrategy.cs index dbb1122deb1..f4cf30357f1 100644 --- a/src/Polly/Caching/ITtlStrategy.cs +++ b/src/Polly/Caching/ITtlStrategy.cs @@ -1,24 +1,23 @@ -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines a strategy for providing time-to-live durations for cacheable results. +/// +public interface ITtlStrategy : ITtlStrategy { - /// - /// Defines a strategy for providing time-to-live durations for cacheable results. - /// - public interface ITtlStrategy : ITtlStrategy - { - - } + +} +/// +/// Defines a strategy for providing time-to-live durations for cacheable results. +/// +public interface ITtlStrategy +{ /// - /// Defines a strategy for providing time-to-live durations for cacheable results. + /// Gets a TTL for a cacheable item, given the current execution context. /// - public interface ITtlStrategy - { - /// - /// Gets a TTL for a cacheable item, given the current execution context. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - Ttl GetTtl(Context context, TResult result); - } + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + Ttl GetTtl(Context context, TResult result); } diff --git a/src/Polly/Caching/NonSlidingTtl.cs b/src/Polly/Caching/NonSlidingTtl.cs index e366524d880..85778163307 100644 --- a/src/Polly/Caching/NonSlidingTtl.cs +++ b/src/Polly/Caching/NonSlidingTtl.cs @@ -1,36 +1,35 @@ using System; using Polly.Utilities; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Represents an expiring at an absolute time, not with sliding expiration. +/// +public abstract class NonSlidingTtl : ITtlStrategy { /// - /// Represents an expiring at an absolute time, not with sliding expiration. + /// The absolute expiration time for cache items, represented by this strategy. /// - public abstract class NonSlidingTtl : ITtlStrategy - { - /// - /// The absolute expiration time for cache items, represented by this strategy. - /// - protected readonly DateTimeOffset absoluteExpirationTime; + protected readonly DateTimeOffset absoluteExpirationTime; - /// - /// Constructs a new instance of the strategy. - /// - /// The absolute expiration time for cache items, represented by this strategy. - protected NonSlidingTtl(DateTimeOffset absoluteExpirationTime) - => this.absoluteExpirationTime = absoluteExpirationTime; + /// + /// Constructs a new instance of the strategy. + /// + /// The absolute expiration time for cache items, represented by this strategy. + protected NonSlidingTtl(DateTimeOffset absoluteExpirationTime) + => this.absoluteExpirationTime = absoluteExpirationTime; - /// - /// Gets a TTL for a cacheable item, given the current execution context. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, object result) - { - TimeSpan untilPointInTime = absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow()); - TimeSpan remaining = untilPointInTime > TimeSpan.Zero ? untilPointInTime : TimeSpan.Zero; - return new Ttl(remaining, false); - } + /// + /// Gets a TTL for a cacheable item, given the current execution context. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + public Ttl GetTtl(Context context, object result) + { + TimeSpan untilPointInTime = absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow()); + TimeSpan remaining = untilPointInTime > TimeSpan.Zero ? untilPointInTime : TimeSpan.Zero; + return new Ttl(remaining, false); } } diff --git a/src/Polly/Caching/RelativeTtl.cs b/src/Polly/Caching/RelativeTtl.cs index 2a2dcf34383..f5d118303da 100644 --- a/src/Polly/Caching/RelativeTtl.cs +++ b/src/Polly/Caching/RelativeTtl.cs @@ -1,31 +1,30 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines a ttl strategy which will cache items for the specified time. +/// +public class RelativeTtl : ITtlStrategy { + private readonly TimeSpan ttl; + /// - /// Defines a ttl strategy which will cache items for the specified time. + /// Initializes a new instance of the class. /// - public class RelativeTtl : ITtlStrategy + /// The timespan for which to consider the cache item valid. + public RelativeTtl(TimeSpan ttl) { - private readonly TimeSpan ttl; - - /// - /// Initializes a new instance of the class. - /// - /// The timespan for which to consider the cache item valid. - public RelativeTtl(TimeSpan ttl) - { - if (ttl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(ttl), "The ttl for items to cache must be greater than zero."); - - this.ttl = ttl; - } + if (ttl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(ttl), "The ttl for items to cache must be greater than zero."); - /// - /// Gets a TTL for a cacheable item, given the current execution context. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, object result) => new Ttl(ttl); + this.ttl = ttl; } + + /// + /// Gets a TTL for a cacheable item, given the current execution context. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + public Ttl GetTtl(Context context, object result) => new Ttl(ttl); } diff --git a/src/Polly/Caching/ResultTtl.cs b/src/Polly/Caching/ResultTtl.cs index cd933add8de..514ffc54cd4 100644 --- a/src/Polly/Caching/ResultTtl.cs +++ b/src/Polly/Caching/ResultTtl.cs @@ -1,39 +1,38 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines a ttl strategy which can calculate a duration to cache items dynamically based on the execution context and result of the execution. +/// +/// The type of results that the ttl calculation function will take as an input parameter. +public class ResultTtl : ITtlStrategy { + private readonly Func _ttlFunc; + /// - /// Defines a ttl strategy which can calculate a duration to cache items dynamically based on the execution context and result of the execution. + /// Constructs a new instance of the ttl strategy, with a func calculating based on the value to cache. /// - /// The type of results that the ttl calculation function will take as an input parameter. - public class ResultTtl : ITtlStrategy + /// The function to calculate the TTL for which cache items should be considered valid. + public ResultTtl(Func ttlFunc) { - private readonly Func _ttlFunc; - - /// - /// Constructs a new instance of the ttl strategy, with a func calculating based on the value to cache. - /// - /// The function to calculate the TTL for which cache items should be considered valid. - public ResultTtl(Func ttlFunc) - { - if (ttlFunc == null) throw new ArgumentNullException(nameof(ttlFunc)); - _ttlFunc = (_, result) => ttlFunc(result); - } + if (ttlFunc == null) throw new ArgumentNullException(nameof(ttlFunc)); + _ttlFunc = (_, result) => ttlFunc(result); + } - /// - /// Constructs a new instance of the ttl strategy, with a func calculating based on the execution and value to cache. - /// - /// The function to calculate the TTL for which cache items should be considered valid. - public ResultTtl(Func ttlFunc) - => _ttlFunc = ttlFunc ?? throw new ArgumentNullException(nameof(ttlFunc)); + /// + /// Constructs a new instance of the ttl strategy, with a func calculating based on the execution and value to cache. + /// + /// The function to calculate the TTL for which cache items should be considered valid. + public ResultTtl(Func ttlFunc) + => _ttlFunc = ttlFunc ?? throw new ArgumentNullException(nameof(ttlFunc)); - /// - /// Gets a TTL for the cacheable item. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. + /// + /// Gets a TTL for the cacheable item. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, TResult result) => _ttlFunc(context, result); - } + public Ttl GetTtl(Context context, TResult result) => _ttlFunc(context, result); } diff --git a/src/Polly/Caching/SerializingCacheProvider.cs b/src/Polly/Caching/SerializingCacheProvider.cs index 9d57a941b29..e3f6f9fd915 100644 --- a/src/Polly/Caching/SerializingCacheProvider.cs +++ b/src/Polly/Caching/SerializingCacheProvider.cs @@ -1,103 +1,102 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with synchronous . +/// +/// The type of serialized objects to be placed in the cache. +public class SerializingCacheProvider : ISyncCacheProvider { + private readonly ISyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; + /// - /// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with synchronous . + /// Initializes a new instance of the class. /// - /// The type of serialized objects to be placed in the cache. - public class SerializingCacheProvider : ISyncCacheProvider + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - private readonly ISyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; - - /// - /// Initializes a new instance of the class. - /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) - { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - } - - /// - /// Gets a value from the cache. - /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (null if not found). - /// - public (bool, object) TryGet(string key) - { - (bool cacheHit, TSerialized objectToDeserialize) = _wrappedCacheProvider.TryGet(key); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); - } - - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - public void Put(string key, object value, Ttl ttl) - { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); - } + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } + /// + /// Gets a value from the cache. + /// + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (null if not found). + /// + public (bool, object) TryGet(string key) + { + (bool cacheHit, TSerialized objectToDeserialize) = _wrappedCacheProvider.TryGet(key); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); } /// - /// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with synchronous . + /// Puts the specified value in the cache. /// - /// The return type of delegates which may be executed through the policy. - /// The type of serialized objects to be placed in the cache. - public class SerializingCacheProvider : ISyncCacheProvider + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + public void Put(string key, object value, Ttl ttl) { - private readonly ISyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); + } - /// - /// Initializes a new instance of the class. - /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) - { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - } +} - /// - /// Gets a value from the cache. - /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (default(TResult) if not found). - /// - public (bool, TResult) TryGet(string key) - { - (bool cacheHit, TSerialized objectToDeserialize) = _wrappedCacheProvider.TryGet(key); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); - } +/// +/// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with synchronous . +/// +/// The return type of delegates which may be executed through the policy. +/// The type of serialized objects to be placed in the cache. +public class SerializingCacheProvider : ISyncCacheProvider +{ + private readonly ISyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - public void Put(string key, TResult value, Ttl ttl) - { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); - } + /// + /// Initializes a new instance of the class. + /// + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) + { + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } + + /// + /// Gets a value from the cache. + /// + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (default(TResult) if not found). + /// + public (bool, TResult) TryGet(string key) + { + (bool cacheHit, TSerialized objectToDeserialize) = _wrappedCacheProvider.TryGet(key); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); + } + /// + /// Puts the specified value in the cache. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + public void Put(string key, TResult value, Ttl ttl) + { + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); } + } diff --git a/src/Polly/Caching/SlidingTtl.cs b/src/Polly/Caching/SlidingTtl.cs index c203480380b..de4df8586af 100644 --- a/src/Polly/Caching/SlidingTtl.cs +++ b/src/Polly/Caching/SlidingTtl.cs @@ -1,33 +1,32 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Defines a ttl strategy which will cache items with a sliding ttl. +/// + +public class SlidingTtl : ITtlStrategy { + private readonly Ttl ttl; + /// - /// Defines a ttl strategy which will cache items with a sliding ttl. + /// Constructs a new instance of the ttl strategy. /// - - public class SlidingTtl : ITtlStrategy + /// The sliding timespan for which cache items should be considered valid. + public SlidingTtl(TimeSpan slidingTtl) { - private readonly Ttl ttl; - - /// - /// Constructs a new instance of the ttl strategy. - /// - /// The sliding timespan for which cache items should be considered valid. - public SlidingTtl(TimeSpan slidingTtl) - { - if (slidingTtl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(slidingTtl), "The ttl for items to cache must be greater than zero."); + if (slidingTtl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(slidingTtl), "The ttl for items to cache must be greater than zero."); - ttl = new Ttl(slidingTtl, true); - } + ttl = new Ttl(slidingTtl, true); + } - /// - /// Gets a TTL for the cacheable item. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. + /// + /// Gets a TTL for the cacheable item. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, object result) => ttl; - } + public Ttl GetTtl(Context context, object result) => ttl; } diff --git a/src/Polly/Caching/Ttl.cs b/src/Polly/Caching/Ttl.cs index 5618917fdbc..7dd10fcbf87 100644 --- a/src/Polly/Caching/Ttl.cs +++ b/src/Polly/Caching/Ttl.cs @@ -1,40 +1,39 @@ using System; -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Represents a time-to-live for a given cache item. +/// +public struct Ttl { /// - /// Represents a time-to-live for a given cache item. + /// The timespan for which this cache-item remains valid. /// - public struct Ttl - { - /// - /// The timespan for which this cache-item remains valid. - /// - public TimeSpan Timespan; + public TimeSpan Timespan; - /// - /// Whether this should be considered as sliding expiration: that is, the cache item should be considered valid for a further period of duration each time the cache item is retrieved. - /// - public bool SlidingExpiration; + /// + /// Whether this should be considered as sliding expiration: that is, the cache item should be considered valid for a further period of duration each time the cache item is retrieved. + /// + public bool SlidingExpiration; - /// - /// Creates a new struct. - /// - /// The timespan for which this cache-item remains valid. - /// Will be considered as not denoting sliding expiration. - public Ttl(TimeSpan timeSpan) : this(timeSpan, false) - { - } + /// + /// Creates a new struct. + /// + /// The timespan for which this cache-item remains valid. + /// Will be considered as not denoting sliding expiration. + public Ttl(TimeSpan timeSpan) : this(timeSpan, false) + { + } - /// - /// Creates a new struct. - /// - /// The timespan for which this cache-item remains valid - /// Whether this should be considered as sliding expiration. - public Ttl(TimeSpan timeSpan, bool slidingExpiration) - { - Timespan = timeSpan; - SlidingExpiration = slidingExpiration; - } + /// + /// Creates a new struct. + /// + /// The timespan for which this cache-item remains valid + /// Whether this should be considered as sliding expiration. + public Ttl(TimeSpan timeSpan, bool slidingExpiration) + { + Timespan = timeSpan; + SlidingExpiration = slidingExpiration; } } diff --git a/src/Polly/Caching/TtlStrategyExtensions.cs b/src/Polly/Caching/TtlStrategyExtensions.cs index 4c742664d5e..ed0a7e0c23e 100644 --- a/src/Polly/Caching/TtlStrategyExtensions.cs +++ b/src/Polly/Caching/TtlStrategyExtensions.cs @@ -1,17 +1,16 @@ -namespace Polly.Caching +namespace Polly.Caching; + +/// +/// Class that provides helper methods for configuring TtlStrategies. +/// +internal static class TtlStrategyExtensions { /// - /// Class that provides helper methods for configuring TtlStrategies. + /// Provides a strongly -typed version of the supplied /// - internal static class TtlStrategyExtensions - { - /// - /// Provides a strongly -typed version of the supplied - /// - /// The type the returned will handle. - /// The non-generic ttl strategy to wrap. - /// ITtlStrategy{TCacheFormat}. - internal static ITtlStrategy For(this ITtlStrategy ttlStrategy) - => new GenericTtlStrategy(ttlStrategy); - } + /// The type the returned will handle. + /// The non-generic ttl strategy to wrap. + /// ITtlStrategy{TCacheFormat}. + internal static ITtlStrategy For(this ITtlStrategy ttlStrategy) + => new GenericTtlStrategy(ttlStrategy); } diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs index 8e707fd7272..9e4cd686179 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs @@ -2,250 +2,249 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class AdvancedCircuitBreakerSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// /// - public static class AdvancedCircuitBreakerSyntax + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) - { - Action doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + return policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); } } diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs index b06eb42bfc8..869f6d6a3ae 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs @@ -2,252 +2,251 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class AdvancedCircuitBreakerTResultSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// /// - public static class AdvancedCircuitBreakerTResultSyntax + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) - { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + return policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The return type of delegates which may be executed through the policy. - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The return type of delegates which may be executed through the policy. - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The return type of delegates which may be executed through the policy. + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The return type of delegates which may be executed through the policy. + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); } } diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitController.cs b/src/Polly/CircuitBreaker/AdvancedCircuitController.cs index 8596d4f6b62..e40bf28f3df 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitController.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitController.cs @@ -1,104 +1,103 @@ using System; using Polly.Utilities; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal class AdvancedCircuitController : CircuitStateController { - internal class AdvancedCircuitController : CircuitStateController + private const short NumberOfWindows = 10; + internal static readonly long ResolutionOfCircuitTimer = TimeSpan.FromMilliseconds(20).Ticks; + + private readonly IHealthMetrics _metrics; + private readonly double _failureThreshold; + private readonly int _minimumThroughput; + + public AdvancedCircuitController( + double failureThreshold, + TimeSpan samplingDuration, + int minimumThroughput, + TimeSpan durationOfBreak, + Action, CircuitState, TimeSpan, Context> onBreak, + Action onReset, + Action onHalfOpen + ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) { - private const short NumberOfWindows = 10; - internal static readonly long ResolutionOfCircuitTimer = TimeSpan.FromMilliseconds(20).Ticks; - - private readonly IHealthMetrics _metrics; - private readonly double _failureThreshold; - private readonly int _minimumThroughput; - - public AdvancedCircuitController( - double failureThreshold, - TimeSpan samplingDuration, - int minimumThroughput, - TimeSpan durationOfBreak, - Action, CircuitState, TimeSpan, Context> onBreak, - Action onReset, - Action onHalfOpen - ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) - { - _metrics = samplingDuration.Ticks < ResolutionOfCircuitTimer * NumberOfWindows - ? (IHealthMetrics)new SingleHealthMetrics(samplingDuration) - : (IHealthMetrics)new RollingHealthMetrics(samplingDuration, NumberOfWindows); + _metrics = samplingDuration.Ticks < ResolutionOfCircuitTimer * NumberOfWindows + ? (IHealthMetrics)new SingleHealthMetrics(samplingDuration) + : (IHealthMetrics)new RollingHealthMetrics(samplingDuration, NumberOfWindows); - _failureThreshold = failureThreshold; - _minimumThroughput = minimumThroughput; - } + _failureThreshold = failureThreshold; + _minimumThroughput = minimumThroughput; + } - public override void OnCircuitReset(Context context) + public override void OnCircuitReset(Context context) + { + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) - { - // Is only null during initialization of the current class - // as the variable is not set, before the base class calls - // current method from constructor. - _metrics?.Reset_NeedsLock(); + // Is only null during initialization of the current class + // as the variable is not set, before the base class calls + // current method from constructor. + _metrics?.Reset_NeedsLock(); - ResetInternal_NeedsLock(context); - } + ResetInternal_NeedsLock(context); } + } - public override void OnActionSuccess(Context context) + public override void OnActionSuccess(Context context) + { + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) + switch (_circuitState) { - switch (_circuitState) - { - case CircuitState.HalfOpen: - OnCircuitReset(context); - break; + case CircuitState.HalfOpen: + OnCircuitReset(context); + break; - case CircuitState.Closed: - break; + case CircuitState.Closed: + break; - case CircuitState.Open: - case CircuitState.Isolated: - break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no special action; only time passing governs transitioning from Open to HalfOpen state. + case CircuitState.Open: + case CircuitState.Isolated: + break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no special action; only time passing governs transitioning from Open to HalfOpen state. - default: - throw new InvalidOperationException("Unhandled CircuitState."); - } - - _metrics.IncrementSuccess_NeedsLock(); + default: + throw new InvalidOperationException("Unhandled CircuitState."); } + + _metrics.IncrementSuccess_NeedsLock(); } + } - public override void OnActionFailure(DelegateResult outcome, Context context) + public override void OnActionFailure(DelegateResult outcome, Context context) + { + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) + _lastOutcome = outcome; + + switch (_circuitState) { - _lastOutcome = outcome; + case CircuitState.HalfOpen: + Break_NeedsLock(context); + return; + + case CircuitState.Closed: + _metrics.IncrementFailure_NeedsLock(); + var healthCount = _metrics.GetHealthCount_NeedsLock(); - switch (_circuitState) - { - case CircuitState.HalfOpen: + int throughput = healthCount.Total; + if (throughput >= _minimumThroughput && ((double)healthCount.Failures) / throughput >= _failureThreshold) + { Break_NeedsLock(context); - return; - - case CircuitState.Closed: - _metrics.IncrementFailure_NeedsLock(); - var healthCount = _metrics.GetHealthCount_NeedsLock(); - - int throughput = healthCount.Total; - if (throughput >= _minimumThroughput && ((double)healthCount.Failures) / throughput >= _failureThreshold) - { - Break_NeedsLock(context); - } - break; - - case CircuitState.Open: - case CircuitState.Isolated: - _metrics.IncrementFailure_NeedsLock(); - break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action beyond tracking the metric; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). - - default: - throw new InvalidOperationException("Unhandled CircuitState."); - } + } + break; + + case CircuitState.Open: + case CircuitState.Isolated: + _metrics.IncrementFailure_NeedsLock(); + break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action beyond tracking the metric; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). + + default: + throw new InvalidOperationException("Unhandled CircuitState."); } } } diff --git a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs index 9bb666e577b..ffc88a12343 100644 --- a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs @@ -2,254 +2,253 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class AsyncAdvancedCircuitBreakerSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// /// - public static class AsyncAdvancedCircuitBreakerSyntax + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) - { - Action doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); } } diff --git a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs index 9f4f2ddf711..1f8472b5846 100644 --- a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs @@ -2,253 +2,252 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class AsyncAdvancedCircuitBreakerTResultSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// /// - public static class AsyncAdvancedCircuitBreakerTResultSyntax + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) - { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs index eadf0f0bd55..fb27f91880e 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs @@ -3,51 +3,50 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal class AsyncCircuitBreakerEngine { - internal class AsyncCircuitBreakerEngine + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + ICircuitController breakerController) { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - ICircuitController breakerController) - { - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); + + breakerController.OnActionPreExecute(); - breakerController.OnActionPreExecute(); + try + { + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - try + if (shouldHandleResultPredicates.AnyMatch(result)) { - TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - - if (shouldHandleResultPredicates.AnyMatch(result)) - { - breakerController.OnActionFailure(new DelegateResult(result), context); - } - else - { - breakerController.OnActionSuccess(context); - } - - return result; + breakerController.OnActionFailure(new DelegateResult(result), context); } - catch (Exception ex) + else { - Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } - - breakerController.OnActionFailure(new DelegateResult(handledException), context); + breakerController.OnActionSuccess(context); + } - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + return result; + } + catch (Exception ex) + { + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { throw; } + + breakerController.OnActionFailure(new DelegateResult(handledException), context); + + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; } } } diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs index 846ee670543..ec179516f23 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs @@ -4,115 +4,114 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +/// +/// A circuit-breaker policy that can be applied to async delegates. +/// +public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy { - /// - /// A circuit-breaker policy that can be applied to async delegates. - /// - public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy + internal readonly ICircuitController _breakerController; + + internal AsyncCircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) { - internal readonly ICircuitController _breakerController; - - internal AsyncCircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - { - _breakerController = breakerController; - } - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - public Exception LastException => _breakerController.LastException; - - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - public void Isolate() => _breakerController.Isolate(); - - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - public void Reset() => _breakerController.Reset(); - - /// - protected override async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - TResult result = default; - await AsyncCircuitBreakerEngine.ImplementationAsync( - async (ctx, ct) => { result = await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, - cancellationToken, - continueOnCapturedContext, - ExceptionPredicates, - ResultPredicates.None, - _breakerController).ConfigureAwait(continueOnCapturedContext); - return result; - } + _breakerController = breakerController; } /// - /// A circuit-breaker policy that can be applied to async delegates. + /// Gets the state of the underlying circuit. + /// + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + public Exception LastException => _breakerController.LastException; + + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. /// - /// The return type of delegates which may be executed through the policy. - public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + protected override async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + TResult result = default; + await AsyncCircuitBreakerEngine.ImplementationAsync( + async (ctx, ct) => { result = await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + continueOnCapturedContext, + ExceptionPredicates, + ResultPredicates.None, + _breakerController).ConfigureAwait(continueOnCapturedContext); + return result; + } +} + +/// +/// A circuit-breaker policy that can be applied to async delegates. +/// +/// The return type of delegates which may be executed through the policy. +public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy +{ + internal readonly ICircuitController _breakerController; + + internal AsyncCircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) { - internal readonly ICircuitController _breakerController; - - internal AsyncCircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - { - _breakerController = breakerController; - } - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - public Exception LastException => _breakerController.LastException; - - /// - /// Gets the last result returned from a user delegate which the circuit-breaker handled. - /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. - /// - public TResult LastHandledResult => _breakerController.LastHandledResult; - - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - public void Isolate() => _breakerController.Isolate(); - - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - public void Reset() => _breakerController.Reset(); - - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncCircuitBreakerEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - ExceptionPredicates, - ResultPredicates, - _breakerController); + _breakerController = breakerController; } + + /// + /// Gets the state of the underlying circuit. + /// + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + public Exception LastException => _breakerController.LastException; + + /// + /// Gets the last result returned from a user delegate which the circuit-breaker handled. + /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. + /// + public TResult LastHandledResult => _breakerController.LastHandledResult; + + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncCircuitBreakerEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + ExceptionPredicates, + ResultPredicates, + _breakerController); } diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs index f111cff360f..2d092ea7bfd 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs @@ -2,217 +2,216 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class AsyncCircuitBreakerSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// /// - public static class AsyncCircuitBreakerSyntax + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) - { - Action doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + return policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new ConsecutiveCountCircuitController( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + var breakerController = new ConsecutiveCountCircuitController( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); } } diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs index 48b8d32344d..a25361f3ff3 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs @@ -1,217 +1,216 @@ using System; using Polly.CircuitBreaker; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class AsyncCircuitBreakerTResultSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// /// - public static class AsyncCircuitBreakerTResultSyntax + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) - { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + return policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new ConsecutiveCountCircuitController( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + var breakerController = new ConsecutiveCountCircuitController( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); } } diff --git a/src/Polly/CircuitBreaker/BrokenCircuitException.cs b/src/Polly/CircuitBreaker/BrokenCircuitException.cs index b19d7aef0b5..61974b58f8d 100644 --- a/src/Polly/CircuitBreaker/BrokenCircuitException.cs +++ b/src/Polly/CircuitBreaker/BrokenCircuitException.cs @@ -4,96 +4,95 @@ using System.Runtime.Serialization; #endif -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +/// +/// Exception thrown when a circuit is broken. +/// +#if NETSTANDARD2_0 +[Serializable] +#endif +public class BrokenCircuitException : ExecutionRejectedException { /// - /// Exception thrown when a circuit is broken. + /// Initializes a new instance of the class. /// -#if NETSTANDARD2_0 - [Serializable] -#endif - public class BrokenCircuitException : ExecutionRejectedException + public BrokenCircuitException() { - /// - /// Initializes a new instance of the class. - /// - public BrokenCircuitException() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public BrokenCircuitException(string message) : base(message) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - /// The inner exception. - public BrokenCircuitException(string message, Exception inner) : base(message, inner) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public BrokenCircuitException(string message) : base(message) + { + } -#if NETSTANDARD2_0 - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected BrokenCircuitException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The inner exception. + public BrokenCircuitException(string message, Exception inner) : base(message, inner) + { } +#if NETSTANDARD2_0 /// - /// Exception thrown when a circuit is broken. + /// Initializes a new instance of the class. /// - /// The type of returned results being handled by the policy. + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected BrokenCircuitException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } +#endif +} + +/// +/// Exception thrown when a circuit is broken. +/// +/// The type of returned results being handled by the policy. #if NETSTANDARD2_0 - [Serializable] +[Serializable] #endif - public class BrokenCircuitException : BrokenCircuitException - { - private readonly TResult result; +public class BrokenCircuitException : BrokenCircuitException +{ + private readonly TResult result; - /// - /// Initializes a new instance of the class. - /// - /// The result which caused the circuit to break. - public BrokenCircuitException(TResult result) : base() - => this.result = result; + /// + /// Initializes a new instance of the class. + /// + /// The result which caused the circuit to break. + public BrokenCircuitException(TResult result) : base() + => this.result = result; - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - /// The result which caused the circuit to break. - public BrokenCircuitException(string message, TResult result) : base(message) - => this.result = result; + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The result which caused the circuit to break. + public BrokenCircuitException(string message, TResult result) : base(message) + => this.result = result; - /// - /// The result value which was considered a handled fault, by the policy. - /// - public TResult Result { get => result; } + /// + /// The result value which was considered a handled fault, by the policy. + /// + public TResult Result { get => result; } #if NETSTANDARD2_0 - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected BrokenCircuitException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected BrokenCircuitException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { } -} \ No newline at end of file +#endif +} diff --git a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs index 141eb4167c7..ad34434ee78 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs @@ -2,53 +2,52 @@ using System.Runtime.ExceptionServices; using System.Threading; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal class CircuitBreakerEngine { - internal class CircuitBreakerEngine + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + ICircuitController breakerController) { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - ICircuitController breakerController) - { - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - breakerController.OnActionPreExecute(); + breakerController.OnActionPreExecute(); + + try + { + TResult result = action(context, cancellationToken); - try + if (shouldHandleResultPredicates.AnyMatch(result)) { - TResult result = action(context, cancellationToken); - - if (shouldHandleResultPredicates.AnyMatch(result)) - { - breakerController.OnActionFailure(new DelegateResult(result), context); - } - else - { - breakerController.OnActionSuccess(context); - } - - return result; + breakerController.OnActionFailure(new DelegateResult(result), context); } - catch (Exception ex) + else + { + breakerController.OnActionSuccess(context); + } + + return result; + } + catch (Exception ex) + { + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) { - Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } - - breakerController.OnActionFailure(new DelegateResult(handledException), context); - - if (handledException != ex) - { - ExceptionDispatchInfo.Capture(handledException).Throw(); - } throw; } + + breakerController.OnActionFailure(new DelegateResult(handledException), context); + + if (handledException != ex) + { + ExceptionDispatchInfo.Capture(handledException).Throw(); + } + throw; } } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs b/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs index 2e37819f383..af202e9663a 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs @@ -3,107 +3,106 @@ using Polly.Utilities; using System.Threading; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +/// +/// A circuit-breaker policy that can be applied to delegates. +/// +public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy { + internal readonly ICircuitController _breakerController; + + internal CircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) + => _breakerController = breakerController; + /// - /// A circuit-breaker policy that can be applied to delegates. + /// Gets the state of the underlying circuit. /// - public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy - { - internal readonly ICircuitController _breakerController; - - internal CircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - => _breakerController = breakerController; - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - public Exception LastException => _breakerController.LastException; - - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - public void Isolate() => _breakerController.Isolate(); - - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - public void Reset() => _breakerController.Reset(); - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - TResult result = default; - CircuitBreakerEngine.Implementation( - (ctx, ct) => { result = action(ctx, ct); return EmptyStruct.Instance; }, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - _breakerController); - return result; - } - } + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + public Exception LastException => _breakerController.LastException; /// - /// A circuit-breaker policy that can be applied to delegates returning a value of type . + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. /// - public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) { - internal readonly ICircuitController _breakerController; - - internal CircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - => _breakerController = breakerController; - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was a handled value. - /// - public Exception LastException => _breakerController.LastException; - - /// - /// Gets the last result returned from a user delegate which the circuit-breaker handled. - /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. - /// - public TResult LastHandledResult => _breakerController.LastHandledResult; - - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - public void Isolate() => _breakerController.Isolate(); - - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - public void Reset() => _breakerController.Reset(); - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => CircuitBreakerEngine.Implementation( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _breakerController); + TResult result = default; + CircuitBreakerEngine.Implementation( + (ctx, ct) => { result = action(ctx, ct); return EmptyStruct.Instance; }, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + _breakerController); + return result; } } + +/// +/// A circuit-breaker policy that can be applied to delegates returning a value of type . +/// +public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy +{ + internal readonly ICircuitController _breakerController; + + internal CircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) + => _breakerController = breakerController; + + /// + /// Gets the state of the underlying circuit. + /// + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was a handled value. + /// + public Exception LastException => _breakerController.LastException; + + /// + /// Gets the last result returned from a user delegate which the circuit-breaker handled. + /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. + /// + public TResult LastHandledResult => _breakerController.LastHandledResult; + + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => CircuitBreakerEngine.Implementation( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _breakerController); +} diff --git a/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs index 124e51bad62..c1819b6c2eb 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs @@ -2,217 +2,216 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class CircuitBreakerSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// /// - public static class CircuitBreakerSyntax + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) - { - Action doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.CircuitBreaker - (exceptionsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.CircuitBreaker( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + return policyBuilder.CircuitBreaker + (exceptionsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreaker(exceptionsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.CircuitBreaker( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreaker(exceptionsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new ConsecutiveCountCircuitController( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + var breakerController = new ConsecutiveCountCircuitController( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs index 69e79cdee53..524c1f22222 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs @@ -1,217 +1,216 @@ using System; using Polly.CircuitBreaker; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Circuit Breaker . +/// +public static class CircuitBreakerTResultSyntax { /// - /// Fluent API for defining a Circuit Breaker . + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// /// - public static class CircuitBreakerTResultSyntax + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) { - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) - { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - Action doNothingOnReset = () => { }; - - return policyBuilder.CircuitBreaker - (handledEventsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + Action doNothingOnReset = () => { }; - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.CircuitBreaker( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + return policyBuilder.CircuitBreaker + (handledEventsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - Action doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreaker(handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.CircuitBreaker( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + Action doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreaker(handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); + + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - ICircuitController breakerController = new ConsecutiveCountCircuitController( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); - } + ICircuitController breakerController = new ConsecutiveCountCircuitController( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/CircuitState.cs b/src/Polly/CircuitBreaker/CircuitState.cs index c02151400e6..177644b90b7 100644 --- a/src/Polly/CircuitBreaker/CircuitState.cs +++ b/src/Polly/CircuitBreaker/CircuitState.cs @@ -1,25 +1,24 @@ -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +/// +/// Describes the possible states the circuit of a CircuitBreaker may be in. +/// +public enum CircuitState { /// - /// Describes the possible states the circuit of a CircuitBreaker may be in. + /// Closed - When the circuit is closed. Execution of actions is allowed. /// - public enum CircuitState - { - /// - /// Closed - When the circuit is closed. Execution of actions is allowed. - /// - Closed, - /// - /// Open - When the automated controller has opened the circuit (typically due to some failure threshold being exceeded by recent actions). Execution of actions is blocked. - /// - Open, - /// - /// Half-open - When the circuit is half-open, it is recovering from an open state. The duration of break of the preceding open state has typically passed. In the half-open state, actions may be executed, but the results of these actions may be treated with criteria different to normal operation, to decide if the circuit has recovered sufficiently to be placed back in to the closed state, or if continuing failures mean the circuit should revert to open perhaps more quickly than in normal operation. - /// - HalfOpen, - /// - /// Isolated - When the circuit has been placed into a fixed open state by a call to . This isolates the circuit manually, blocking execution of all actions until a call to is made. - /// - Isolated - } + Closed, + /// + /// Open - When the automated controller has opened the circuit (typically due to some failure threshold being exceeded by recent actions). Execution of actions is blocked. + /// + Open, + /// + /// Half-open - When the circuit is half-open, it is recovering from an open state. The duration of break of the preceding open state has typically passed. In the half-open state, actions may be executed, but the results of these actions may be treated with criteria different to normal operation, to decide if the circuit has recovered sufficiently to be placed back in to the closed state, or if continuing failures mean the circuit should revert to open perhaps more quickly than in normal operation. + /// + HalfOpen, + /// + /// Isolated - When the circuit has been placed into a fixed open state by a call to . This isolates the circuit manually, blocking execution of all actions until a call to is made. + /// + Isolated } diff --git a/src/Polly/CircuitBreaker/CircuitStateController.cs b/src/Polly/CircuitBreaker/CircuitStateController.cs index d6fdaaf07d0..8a6b148299f 100644 --- a/src/Polly/CircuitBreaker/CircuitStateController.cs +++ b/src/Polly/CircuitBreaker/CircuitStateController.cs @@ -2,181 +2,180 @@ using System.Threading; using Polly.Utilities; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal abstract class CircuitStateController : ICircuitController { - internal abstract class CircuitStateController : ICircuitController + protected readonly TimeSpan _durationOfBreak; + protected long _blockedTill; + protected CircuitState _circuitState; + protected DelegateResult _lastOutcome; + + protected readonly Action, CircuitState, TimeSpan, Context> _onBreak; + protected readonly Action _onReset; + protected readonly Action _onHalfOpen; + + protected readonly object _lock = new object(); + + protected CircuitStateController( + TimeSpan durationOfBreak, + Action, CircuitState, TimeSpan, Context> onBreak, + Action onReset, + Action onHalfOpen) { - protected readonly TimeSpan _durationOfBreak; - protected long _blockedTill; - protected CircuitState _circuitState; - protected DelegateResult _lastOutcome; - - protected readonly Action, CircuitState, TimeSpan, Context> _onBreak; - protected readonly Action _onReset; - protected readonly Action _onHalfOpen; - - protected readonly object _lock = new object(); - - protected CircuitStateController( - TimeSpan durationOfBreak, - Action, CircuitState, TimeSpan, Context> onBreak, - Action onReset, - Action onHalfOpen) - { - _durationOfBreak = durationOfBreak; - _onBreak = onBreak; - _onReset = onReset; - _onHalfOpen = onHalfOpen; + _durationOfBreak = durationOfBreak; + _onBreak = onBreak; + _onReset = onReset; + _onHalfOpen = onHalfOpen; - _circuitState = CircuitState.Closed; - Reset(); - } + _circuitState = CircuitState.Closed; + Reset(); + } - public CircuitState CircuitState + public CircuitState CircuitState + { + get { - get + if (_circuitState != CircuitState.Open) { - if (_circuitState != CircuitState.Open) - { - return _circuitState; - } - - using (TimedLock.Lock(_lock)) - { - if (_circuitState == CircuitState.Open && !IsInAutomatedBreak_NeedsLock) - { - _circuitState = CircuitState.HalfOpen; - _onHalfOpen(); - } - return _circuitState; - } + return _circuitState; } - } - public Exception LastException - { - get + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) + if (_circuitState == CircuitState.Open && !IsInAutomatedBreak_NeedsLock) { - return _lastOutcome?.Exception; + _circuitState = CircuitState.HalfOpen; + _onHalfOpen(); } + return _circuitState; } } + } - public TResult LastHandledResult + public Exception LastException + { + get { - get + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) - { - return _lastOutcome != null - ? _lastOutcome.Result : default; - } + return _lastOutcome?.Exception; } } + } - protected bool IsInAutomatedBreak_NeedsLock + public TResult LastHandledResult + { + get { - get + using (TimedLock.Lock(_lock)) { - return SystemClock.UtcNow().Ticks < _blockedTill; + return _lastOutcome != null + ? _lastOutcome.Result : default; } } + } - public void Isolate() + protected bool IsInAutomatedBreak_NeedsLock + { + get { - using (TimedLock.Lock(_lock)) - { - _lastOutcome = new DelegateResult(new IsolatedCircuitException("The circuit is manually held open and is not allowing calls.")); - BreakFor_NeedsLock(TimeSpan.MaxValue, Context.None()); - _circuitState = CircuitState.Isolated; - } + return SystemClock.UtcNow().Ticks < _blockedTill; } + } - protected void Break_NeedsLock(Context context) => BreakFor_NeedsLock(_durationOfBreak, context); - - private void BreakFor_NeedsLock(TimeSpan durationOfBreak, Context context) + public void Isolate() + { + using (TimedLock.Lock(_lock)) { - bool willDurationTakeUsPastDateTimeMaxValue = durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow(); - _blockedTill = willDurationTakeUsPastDateTimeMaxValue - ? DateTime.MaxValue.Ticks - : (SystemClock.UtcNow() + durationOfBreak).Ticks; + _lastOutcome = new DelegateResult(new IsolatedCircuitException("The circuit is manually held open and is not allowing calls.")); + BreakFor_NeedsLock(TimeSpan.MaxValue, Context.None()); + _circuitState = CircuitState.Isolated; + } + } - var transitionedState = _circuitState; - _circuitState = CircuitState.Open; + protected void Break_NeedsLock(Context context) => BreakFor_NeedsLock(_durationOfBreak, context); - _onBreak(_lastOutcome, transitionedState, durationOfBreak, context); - } + private void BreakFor_NeedsLock(TimeSpan durationOfBreak, Context context) + { + bool willDurationTakeUsPastDateTimeMaxValue = durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow(); + _blockedTill = willDurationTakeUsPastDateTimeMaxValue + ? DateTime.MaxValue.Ticks + : (SystemClock.UtcNow() + durationOfBreak).Ticks; - public void Reset() => OnCircuitReset(Context.None()); + var transitionedState = _circuitState; + _circuitState = CircuitState.Open; - protected void ResetInternal_NeedsLock(Context context) - { - _blockedTill = DateTime.MinValue.Ticks; - _lastOutcome = null; + _onBreak(_lastOutcome, transitionedState, durationOfBreak, context); + } - CircuitState priorState = _circuitState; - _circuitState = CircuitState.Closed; - if (priorState != CircuitState.Closed) - { - _onReset(context); - } - } + public void Reset() => OnCircuitReset(Context.None()); - protected bool PermitHalfOpenCircuitTest() + protected void ResetInternal_NeedsLock(Context context) + { + _blockedTill = DateTime.MinValue.Ticks; + _lastOutcome = null; + + CircuitState priorState = _circuitState; + _circuitState = CircuitState.Closed; + if (priorState != CircuitState.Closed) { - long currentlyBlockedUntil = _blockedTill; - if (SystemClock.UtcNow().Ticks >= currentlyBlockedUntil) - { - // It's time to permit a / another trial call in the half-open state ... - // ... but to prevent race conditions/multiple calls, we have to ensure only _one_ thread wins the race to own this next call. - return Interlocked.CompareExchange(ref _blockedTill, SystemClock.UtcNow().Ticks + _durationOfBreak.Ticks, currentlyBlockedUntil) == currentlyBlockedUntil; - } - return false; + _onReset(context); } + } - private BrokenCircuitException GetBreakingException() + protected bool PermitHalfOpenCircuitTest() + { + long currentlyBlockedUntil = _blockedTill; + if (SystemClock.UtcNow().Ticks >= currentlyBlockedUntil) { - const string BrokenCircuitMessage = "The circuit is now open and is not allowing calls."; + // It's time to permit a / another trial call in the half-open state ... + // ... but to prevent race conditions/multiple calls, we have to ensure only _one_ thread wins the race to own this next call. + return Interlocked.CompareExchange(ref _blockedTill, SystemClock.UtcNow().Ticks + _durationOfBreak.Ticks, currentlyBlockedUntil) == currentlyBlockedUntil; + } + return false; + } - var lastOutcome = _lastOutcome; - if (lastOutcome == null) - { - return new BrokenCircuitException(BrokenCircuitMessage); - } + private BrokenCircuitException GetBreakingException() + { + const string BrokenCircuitMessage = "The circuit is now open and is not allowing calls."; - if (lastOutcome.Exception != null) - { - return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Exception); - } + var lastOutcome = _lastOutcome; + if (lastOutcome == null) + { + return new BrokenCircuitException(BrokenCircuitMessage); + } - return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Result); + if (lastOutcome.Exception != null) + { + return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Exception); } - public void OnActionPreExecute() + return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Result); + } + + public void OnActionPreExecute() + { + switch (CircuitState) { - switch (CircuitState) - { - case CircuitState.Closed: - break; - case CircuitState.HalfOpen: - if (!PermitHalfOpenCircuitTest()) { throw GetBreakingException(); } - break; - case CircuitState.Open: - throw GetBreakingException(); - case CircuitState.Isolated: - throw new IsolatedCircuitException("The circuit is manually held open and is not allowing calls."); - default: - throw new InvalidOperationException("Unhandled CircuitState."); - } + case CircuitState.Closed: + break; + case CircuitState.HalfOpen: + if (!PermitHalfOpenCircuitTest()) { throw GetBreakingException(); } + break; + case CircuitState.Open: + throw GetBreakingException(); + case CircuitState.Isolated: + throw new IsolatedCircuitException("The circuit is manually held open and is not allowing calls."); + default: + throw new InvalidOperationException("Unhandled CircuitState."); } + } - public abstract void OnActionSuccess(Context context); + public abstract void OnActionSuccess(Context context); - public abstract void OnActionFailure(DelegateResult outcome, Context context); + public abstract void OnActionFailure(DelegateResult outcome, Context context); - public abstract void OnCircuitReset(Context context); - } + public abstract void OnCircuitReset(Context context); } diff --git a/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs b/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs index 78a504dd92c..ee93a6ef664 100644 --- a/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs +++ b/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs @@ -1,85 +1,84 @@ using System; using Polly.Utilities; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal class ConsecutiveCountCircuitController : CircuitStateController { - internal class ConsecutiveCountCircuitController : CircuitStateController - { - private readonly int _exceptionsAllowedBeforeBreaking; - private int _consecutiveFailureCount; + private readonly int _exceptionsAllowedBeforeBreaking; + private int _consecutiveFailureCount; - public ConsecutiveCountCircuitController( - int exceptionsAllowedBeforeBreaking, - TimeSpan durationOfBreak, - Action, CircuitState, TimeSpan, Context> onBreak, - Action onReset, - Action onHalfOpen - ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) - { - _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - } + public ConsecutiveCountCircuitController( + int exceptionsAllowedBeforeBreaking, + TimeSpan durationOfBreak, + Action, CircuitState, TimeSpan, Context> onBreak, + Action onReset, + Action onHalfOpen + ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) + { + _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + } - public override void OnCircuitReset(Context context) + public override void OnCircuitReset(Context context) + { + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) - { - _consecutiveFailureCount = 0; + _consecutiveFailureCount = 0; - ResetInternal_NeedsLock(context); - } + ResetInternal_NeedsLock(context); } + } - public override void OnActionSuccess(Context context) + public override void OnActionSuccess(Context context) + { + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) + switch (_circuitState) { - switch (_circuitState) - { - case CircuitState.HalfOpen: - OnCircuitReset(context); - break; + case CircuitState.HalfOpen: + OnCircuitReset(context); + break; - case CircuitState.Closed: - _consecutiveFailureCount = 0; - break; + case CircuitState.Closed: + _consecutiveFailureCount = 0; + break; - case CircuitState.Open: - case CircuitState.Isolated: - break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; only time passing governs transitioning from Open to HalfOpen state. + case CircuitState.Open: + case CircuitState.Isolated: + break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; only time passing governs transitioning from Open to HalfOpen state. - default: - throw new InvalidOperationException("Unhandled CircuitState."); - } + default: + throw new InvalidOperationException("Unhandled CircuitState."); } } + } - public override void OnActionFailure(DelegateResult outcome, Context context) + public override void OnActionFailure(DelegateResult outcome, Context context) + { + using (TimedLock.Lock(_lock)) { - using (TimedLock.Lock(_lock)) + _lastOutcome = outcome; + + switch (_circuitState) { - _lastOutcome = outcome; + case CircuitState.HalfOpen: + Break_NeedsLock(context); + return; - switch (_circuitState) - { - case CircuitState.HalfOpen: + case CircuitState.Closed: + _consecutiveFailureCount += 1; + if (_consecutiveFailureCount >= _exceptionsAllowedBeforeBreaking) + { Break_NeedsLock(context); - return; - - case CircuitState.Closed: - _consecutiveFailureCount += 1; - if (_consecutiveFailureCount >= _exceptionsAllowedBeforeBreaking) - { - Break_NeedsLock(context); - } - break; + } + break; - case CircuitState.Open: - case CircuitState.Isolated: - break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). + case CircuitState.Open: + case CircuitState.Isolated: + break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). - default: - throw new InvalidOperationException("Unhandled CircuitState."); - } + default: + throw new InvalidOperationException("Unhandled CircuitState."); } } } diff --git a/src/Polly/CircuitBreaker/HealthCount.cs b/src/Polly/CircuitBreaker/HealthCount.cs index e5112ca4a1b..8a6f7ea46f0 100644 --- a/src/Polly/CircuitBreaker/HealthCount.cs +++ b/src/Polly/CircuitBreaker/HealthCount.cs @@ -1,13 +1,12 @@ -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal class HealthCount { - internal class HealthCount - { - public int Successes { get; set; } + public int Successes { get; set; } - public int Failures { get; set; } + public int Failures { get; set; } - public int Total { get { return Successes + Failures; } } + public int Total { get { return Successes + Failures; } } - public long StartedAt { get; set; } - } + public long StartedAt { get; set; } } diff --git a/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs b/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs index b19922e6779..73adf6de2e6 100644 --- a/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs +++ b/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs @@ -1,43 +1,42 @@ using System; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +/// +/// Defines properties and methods common to all circuit-breaker policies. +/// +public interface ICircuitBreakerPolicy : IsPolicy { /// - /// Defines properties and methods common to all circuit-breaker policies. + /// Gets the state of the underlying circuit. /// - public interface ICircuitBreakerPolicy : IsPolicy - { - /// - /// Gets the state of the underlying circuit. - /// - CircuitState CircuitState { get; } + CircuitState CircuitState { get; } - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - Exception LastException { get; } + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + Exception LastException { get; } - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - void Isolate(); + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + void Isolate(); - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - void Reset(); - } + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + void Reset(); +} +/// +/// Defines properties and methods common to all circuit-breaker policies generic-typed for executions returning results of type . +/// +public interface ICircuitBreakerPolicy : ICircuitBreakerPolicy +{ /// - /// Defines properties and methods common to all circuit-breaker policies generic-typed for executions returning results of type . + /// Gets the last result returned from a user delegate which the circuit-breaker handled. + /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. /// - public interface ICircuitBreakerPolicy : ICircuitBreakerPolicy - { - /// - /// Gets the last result returned from a user delegate which the circuit-breaker handled. - /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. - /// - TResult LastHandledResult { get; } - } + TResult LastHandledResult { get; } } diff --git a/src/Polly/CircuitBreaker/ICircuitController.cs b/src/Polly/CircuitBreaker/ICircuitController.cs index a24e1364593..ffa28137c23 100644 --- a/src/Polly/CircuitBreaker/ICircuitController.cs +++ b/src/Polly/CircuitBreaker/ICircuitController.cs @@ -1,17 +1,16 @@ using System; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal interface ICircuitController { - internal interface ICircuitController - { - CircuitState CircuitState { get; } - Exception LastException { get; } - TResult LastHandledResult { get; } - void Isolate(); - void Reset(); - void OnCircuitReset(Context context); - void OnActionPreExecute(); - void OnActionSuccess(Context context); - void OnActionFailure(DelegateResult outcome, Context context); - } + CircuitState CircuitState { get; } + Exception LastException { get; } + TResult LastHandledResult { get; } + void Isolate(); + void Reset(); + void OnCircuitReset(Context context); + void OnActionPreExecute(); + void OnActionSuccess(Context context); + void OnActionFailure(DelegateResult outcome, Context context); } diff --git a/src/Polly/CircuitBreaker/IHealthMetrics.cs b/src/Polly/CircuitBreaker/IHealthMetrics.cs index bb131059642..0df590f49ee 100644 --- a/src/Polly/CircuitBreaker/IHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/IHealthMetrics.cs @@ -1,12 +1,11 @@ -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal interface IHealthMetrics { - internal interface IHealthMetrics - { - void IncrementSuccess_NeedsLock(); - void IncrementFailure_NeedsLock(); + void IncrementSuccess_NeedsLock(); + void IncrementFailure_NeedsLock(); - void Reset_NeedsLock(); + void Reset_NeedsLock(); - HealthCount GetHealthCount_NeedsLock(); - } + HealthCount GetHealthCount_NeedsLock(); } diff --git a/src/Polly/CircuitBreaker/IsolatedCircuitException.cs b/src/Polly/CircuitBreaker/IsolatedCircuitException.cs index 127948aea92..ee8e9f7d79f 100644 --- a/src/Polly/CircuitBreaker/IsolatedCircuitException.cs +++ b/src/Polly/CircuitBreaker/IsolatedCircuitException.cs @@ -3,33 +3,32 @@ using System.Runtime.Serialization; #endif -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +/// +/// Exception thrown when a circuit is isolated (held open) by manual override. +/// +#if NETSTANDARD2_0 +[Serializable] +#endif +public class IsolatedCircuitException : BrokenCircuitException { /// - /// Exception thrown when a circuit is isolated (held open) by manual override. + /// Initializes a new instance of the class. /// -#if NETSTANDARD2_0 - [Serializable] -#endif - public class IsolatedCircuitException : BrokenCircuitException - { - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public IsolatedCircuitException(string message) : base(message) { } + /// The message that describes the error. + public IsolatedCircuitException(string message) : base(message) { } #if NETSTANDARD2_0 - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected IsolatedCircuitException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected IsolatedCircuitException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { } +#endif } diff --git a/src/Polly/CircuitBreaker/RollingHealthMetrics.cs b/src/Polly/CircuitBreaker/RollingHealthMetrics.cs index 9b25b2d45fa..1e41e113fe3 100644 --- a/src/Polly/CircuitBreaker/RollingHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/RollingHealthMetrics.cs @@ -2,75 +2,74 @@ using System.Collections.Generic; using Polly.Utilities; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal class RollingHealthMetrics : IHealthMetrics { - internal class RollingHealthMetrics : IHealthMetrics + private readonly long _samplingDuration; + private readonly long _windowDuration; + private readonly Queue _windows; + + private HealthCount _currentWindow; + + public RollingHealthMetrics(TimeSpan samplingDuration, short numberOfWindows) { - private readonly long _samplingDuration; - private readonly long _windowDuration; - private readonly Queue _windows; + _samplingDuration = samplingDuration.Ticks; - private HealthCount _currentWindow; + _windowDuration = _samplingDuration / numberOfWindows; + _windows = new Queue(numberOfWindows + 1); + } - public RollingHealthMetrics(TimeSpan samplingDuration, short numberOfWindows) - { - _samplingDuration = samplingDuration.Ticks; + public void IncrementSuccess_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - _windowDuration = _samplingDuration / numberOfWindows; - _windows = new Queue(numberOfWindows + 1); - } + _currentWindow.Successes++; + } - public void IncrementSuccess_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public void IncrementFailure_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - _currentWindow.Successes++; - } + _currentWindow.Failures++; + } - public void IncrementFailure_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public void Reset_NeedsLock() + { + _currentWindow = null; + _windows.Clear(); + } - _currentWindow.Failures++; - } + public HealthCount GetHealthCount_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - public void Reset_NeedsLock() + int successes = 0; + int failures = 0; + foreach (var window in _windows) { - _currentWindow = null; - _windows.Clear(); + successes += window.Successes; + failures += window.Failures; } - public HealthCount GetHealthCount_NeedsLock() + return new HealthCount { - ActualiseCurrentMetric_NeedsLock(); - - int successes = 0; - int failures = 0; - foreach (var window in _windows) - { - successes += window.Successes; - failures += window.Failures; - } - - return new HealthCount - { - Successes = successes, - Failures = failures, - StartedAt = _windows.Peek().StartedAt - }; - } + Successes = successes, + Failures = failures, + StartedAt = _windows.Peek().StartedAt + }; + } - private void ActualiseCurrentMetric_NeedsLock() + private void ActualiseCurrentMetric_NeedsLock() + { + long now = SystemClock.UtcNow().Ticks; + if (_currentWindow == null || now - _currentWindow.StartedAt >= _windowDuration) { - long now = SystemClock.UtcNow().Ticks; - if (_currentWindow == null || now - _currentWindow.StartedAt >= _windowDuration) - { - _currentWindow = new HealthCount { StartedAt = now }; - _windows.Enqueue(_currentWindow); - } - - while (_windows.Count > 0 && (now - _windows.Peek().StartedAt >= _samplingDuration)) - _windows.Dequeue(); + _currentWindow = new HealthCount { StartedAt = now }; + _windows.Enqueue(_currentWindow); } + + while (_windows.Count > 0 && (now - _windows.Peek().StartedAt >= _samplingDuration)) + _windows.Dequeue(); } } diff --git a/src/Polly/CircuitBreaker/SingleHealthMetrics.cs b/src/Polly/CircuitBreaker/SingleHealthMetrics.cs index 4a604389620..67be8f3be90 100644 --- a/src/Polly/CircuitBreaker/SingleHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/SingleHealthMetrics.cs @@ -1,46 +1,45 @@ using System; using Polly.Utilities; -namespace Polly.CircuitBreaker +namespace Polly.CircuitBreaker; + +internal class SingleHealthMetrics : IHealthMetrics { - internal class SingleHealthMetrics : IHealthMetrics - { - private readonly long _samplingDuration; + private readonly long _samplingDuration; - private HealthCount _current; + private HealthCount _current; - public SingleHealthMetrics(TimeSpan samplingDuration) => _samplingDuration = samplingDuration.Ticks; + public SingleHealthMetrics(TimeSpan samplingDuration) => _samplingDuration = samplingDuration.Ticks; - public void IncrementSuccess_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public void IncrementSuccess_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - _current.Successes++; - } + _current.Successes++; + } - public void IncrementFailure_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public void IncrementFailure_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - _current.Failures++; - } + _current.Failures++; + } - public void Reset_NeedsLock() => _current = null; + public void Reset_NeedsLock() => _current = null; - public HealthCount GetHealthCount_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public HealthCount GetHealthCount_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - return _current; - } + return _current; + } - private void ActualiseCurrentMetric_NeedsLock() + private void ActualiseCurrentMetric_NeedsLock() + { + long now = SystemClock.UtcNow().Ticks; + if (_current == null || now - _current.StartedAt >= _samplingDuration) { - long now = SystemClock.UtcNow().Ticks; - if (_current == null || now - _current.StartedAt >= _samplingDuration) - { - _current = new HealthCount { StartedAt = now }; - } + _current = new HealthCount { StartedAt = now }; } } } diff --git a/src/Polly/Context.Dictionary.cs b/src/Polly/Context.Dictionary.cs index 12ed0043927..60c35e4a7fc 100644 --- a/src/Polly/Context.Dictionary.cs +++ b/src/Polly/Context.Dictionary.cs @@ -2,138 +2,137 @@ using System.Collections; using System.Collections.Generic; -namespace Polly +namespace Polly; + +/// +/// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. +/// Do not re-use an instance of across more than one execution. +/// +public partial class Context : IDictionary, IDictionary, IReadOnlyDictionary { - /// - /// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. - /// Do not re-use an instance of across more than one execution. - /// - public partial class Context : IDictionary, IDictionary, IReadOnlyDictionary - { - // For an individual execution through a policy or policywrap, it is expected that all execution steps (for example executing the user delegate, invoking policy-activity delegates such as onRetry, onBreak, onTimeout etc) execute sequentially. - // Therefore, this class is intentionally not constructed to be safe for concurrent access from multiple threads. + // For an individual execution through a policy or policywrap, it is expected that all execution steps (for example executing the user delegate, invoking policy-activity delegates such as onRetry, onBreak, onTimeout etc) execute sequentially. + // Therefore, this class is intentionally not constructed to be safe for concurrent access from multiple threads. - private Dictionary wrappedDictionary; + private Dictionary wrappedDictionary; - private Dictionary WrappedDictionary => wrappedDictionary ?? (wrappedDictionary = new Dictionary()); + private Dictionary WrappedDictionary => wrappedDictionary ?? (wrappedDictionary = new Dictionary()); - /// - /// Initializes a new instance of the class, with the specified and the supplied . - /// - /// The operation key. - /// The context data. - public Context(string operationKey, IDictionary contextData) : this(contextData) - { - OperationKey = operationKey; - } + /// + /// Initializes a new instance of the class, with the specified and the supplied . + /// + /// The operation key. + /// The context data. + public Context(string operationKey, IDictionary contextData) : this(contextData) + { + OperationKey = operationKey; + } - internal Context(IDictionary contextData) : this() - { - if (contextData == null) throw new ArgumentNullException(nameof(contextData)); - wrappedDictionary = new Dictionary(contextData); - } + internal Context(IDictionary contextData) : this() + { + if (contextData == null) throw new ArgumentNullException(nameof(contextData)); + wrappedDictionary = new Dictionary(contextData); + } #region IDictionary implementation - /// - public ICollection Keys => WrappedDictionary.Keys; + /// + public ICollection Keys => WrappedDictionary.Keys; - /// - public ICollection Values => WrappedDictionary.Values; + /// + public ICollection Values => WrappedDictionary.Values; - /// - public int Count => WrappedDictionary.Count; + /// + public int Count => WrappedDictionary.Count; - /// - bool ICollection>.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; + /// + bool ICollection>.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; - /// - public object this[string key] - { - get => WrappedDictionary[key]; - set => WrappedDictionary[key] = value; - } + /// + public object this[string key] + { + get => WrappedDictionary[key]; + set => WrappedDictionary[key] = value; + } - /// - public void Add(string key, object value) - { - WrappedDictionary.Add(key, value); - } + /// + public void Add(string key, object value) + { + WrappedDictionary.Add(key, value); + } - /// - public bool ContainsKey(string key) => WrappedDictionary.ContainsKey(key); + /// + public bool ContainsKey(string key) => WrappedDictionary.ContainsKey(key); - /// - public bool Remove(string key) => WrappedDictionary.Remove(key); + /// + public bool Remove(string key) => WrappedDictionary.Remove(key); - /// - public bool TryGetValue(string key, out object value) => WrappedDictionary.TryGetValue(key, out value); + /// + public bool TryGetValue(string key, out object value) => WrappedDictionary.TryGetValue(key, out value); - /// - void ICollection>.Add(KeyValuePair item) => ((IDictionary)WrappedDictionary).Add(item); + /// + void ICollection>.Add(KeyValuePair item) => ((IDictionary)WrappedDictionary).Add(item); - /// - public void Clear() => WrappedDictionary.Clear(); + /// + public void Clear() => WrappedDictionary.Clear(); - /// - bool ICollection>.Contains(KeyValuePair item) => ((IDictionary)WrappedDictionary).Contains(item); + /// + bool ICollection>.Contains(KeyValuePair item) => ((IDictionary)WrappedDictionary).Contains(item); - /// - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((IDictionary) WrappedDictionary).CopyTo(array, arrayIndex); + /// + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((IDictionary) WrappedDictionary).CopyTo(array, arrayIndex); - /// - bool ICollection>.Remove(KeyValuePair item) => ((IDictionary)WrappedDictionary).Remove(item); + /// + bool ICollection>.Remove(KeyValuePair item) => ((IDictionary)WrappedDictionary).Remove(item); - /// - public IEnumerator> GetEnumerator() => WrappedDictionary.GetEnumerator(); + /// + public IEnumerator> GetEnumerator() => WrappedDictionary.GetEnumerator(); - /// - IEnumerator IEnumerable.GetEnumerator() => WrappedDictionary.GetEnumerator(); + /// + IEnumerator IEnumerable.GetEnumerator() => WrappedDictionary.GetEnumerator(); - /// - public void Add(object key, object value) => ((IDictionary)WrappedDictionary).Add(key, value); + /// + public void Add(object key, object value) => ((IDictionary)WrappedDictionary).Add(key, value); - /// - public bool Contains(object key) => ((IDictionary)WrappedDictionary).Contains(key); + /// + public bool Contains(object key) => ((IDictionary)WrappedDictionary).Contains(key); - /// - IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)WrappedDictionary).GetEnumerator(); + /// + IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)WrappedDictionary).GetEnumerator(); - /// - public void Remove(object key) => ((IDictionary)WrappedDictionary).Remove(key); + /// + public void Remove(object key) => ((IDictionary)WrappedDictionary).Remove(key); - /// - public void CopyTo(Array array, int index) => ((IDictionary)WrappedDictionary).CopyTo(array, index); + /// + public void CopyTo(Array array, int index) => ((IDictionary)WrappedDictionary).CopyTo(array, index); - #endregion + #endregion - #region IReadOnlyDictionary implementation - IEnumerable IReadOnlyDictionary.Keys => ((IReadOnlyDictionary)WrappedDictionary).Keys; + #region IReadOnlyDictionary implementation + IEnumerable IReadOnlyDictionary.Keys => ((IReadOnlyDictionary)WrappedDictionary).Keys; - IEnumerable IReadOnlyDictionary.Values => ((IReadOnlyDictionary)WrappedDictionary).Values; - #endregion + IEnumerable IReadOnlyDictionary.Values => ((IReadOnlyDictionary)WrappedDictionary).Values; + #endregion - #region IDictionary implementation + #region IDictionary implementation - /// - bool IDictionary.IsFixedSize => ((IDictionary)WrappedDictionary).IsFixedSize; + /// + bool IDictionary.IsFixedSize => ((IDictionary)WrappedDictionary).IsFixedSize; - /// - bool IDictionary.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; + /// + bool IDictionary.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; - ICollection IDictionary.Keys => ((IDictionary)WrappedDictionary).Keys; + ICollection IDictionary.Keys => ((IDictionary)WrappedDictionary).Keys; - ICollection IDictionary.Values => ((IDictionary)WrappedDictionary).Values; + ICollection IDictionary.Values => ((IDictionary)WrappedDictionary).Values; - /// - bool ICollection.IsSynchronized => ((IDictionary)WrappedDictionary).IsSynchronized; + /// + bool ICollection.IsSynchronized => ((IDictionary)WrappedDictionary).IsSynchronized; - /// - object ICollection.SyncRoot => ((IDictionary)WrappedDictionary).SyncRoot; + /// + object ICollection.SyncRoot => ((IDictionary)WrappedDictionary).SyncRoot; - /// - object IDictionary.this[object key] { get => ((IDictionary)WrappedDictionary)[key]; set => ((IDictionary)WrappedDictionary)[key] = value; } + /// + object IDictionary.this[object key] { get => ((IDictionary)WrappedDictionary)[key]; set => ((IDictionary)WrappedDictionary)[key] = value; } #endregion - } -} \ No newline at end of file +} diff --git a/src/Polly/Context.cs b/src/Polly/Context.cs index 65d0949eca1..d814dc77b99 100644 --- a/src/Polly/Context.cs +++ b/src/Polly/Context.cs @@ -1,59 +1,58 @@ using System; using Polly.Wrap; -namespace Polly +namespace Polly; + +/// +/// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. +/// Do not re-use an instance of across more than one call through .Execute(...) or .ExecuteAsync(...). +/// +public partial class Context { + internal static Context None() => new Context(); + + private Guid? _correlationId; + /// - /// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. - /// Do not re-use an instance of across more than one call through .Execute(...) or .ExecuteAsync(...). + /// Initializes a new instance of the class, with the specified . /// - public partial class Context + /// The operation key. + public Context(string operationKey) => OperationKey = operationKey; + + /// + /// Initializes a new instance of the class. + /// + public Context() { - internal static Context None() => new Context(); + } - private Guid? _correlationId; + /// + /// When execution is through a , identifies the PolicyWrap executing the current delegate by returning the of the outermost layer in the PolicyWrap; otherwise, null. + /// + public string PolicyWrapKey { get; internal set; } - /// - /// Initializes a new instance of the class, with the specified . - /// - /// The operation key. - public Context(string operationKey) => OperationKey = operationKey; + /// + /// The of the policy instance executing the current delegate. + /// + public string PolicyKey { get; internal set; } - /// - /// Initializes a new instance of the class. - /// - public Context() - { - } + /// + /// A key unique to the call site of the current execution. + /// Policy instances are commonly reused across multiple call sites. Set an OperationKey so that logging and metrics can distinguish usages of policy instances at different call sites. + /// The value is set by using the constructor taking an operationKey parameter. + /// + public string OperationKey { get; } - /// - /// When execution is through a , identifies the PolicyWrap executing the current delegate by returning the of the outermost layer in the PolicyWrap; otherwise, null. - /// - public string PolicyWrapKey { get; internal set; } - - /// - /// The of the policy instance executing the current delegate. - /// - public string PolicyKey { get; internal set; } - - /// - /// A key unique to the call site of the current execution. - /// Policy instances are commonly reused across multiple call sites. Set an OperationKey so that logging and metrics can distinguish usages of policy instances at different call sites. - /// The value is set by using the constructor taking an operationKey parameter. - /// - public string OperationKey { get; } - - /// - /// A Guid guaranteed to be unique to each execution. - /// Acts as a correlation id so that events specific to a single execution can be identified in logging and telemetry. - /// - public Guid CorrelationId + /// + /// A Guid guaranteed to be unique to each execution. + /// Acts as a correlation id so that events specific to a single execution can be identified in logging and telemetry. + /// + public Guid CorrelationId + { + get { - get - { - if (!_correlationId.HasValue) { _correlationId = Guid.NewGuid(); } - return _correlationId.Value; - } + if (!_correlationId.HasValue) { _correlationId = Guid.NewGuid(); } + return _correlationId.Value; } } } diff --git a/src/Polly/DelegateResult.cs b/src/Polly/DelegateResult.cs index 5cdee1de360..ecde18d9bc4 100644 --- a/src/Polly/DelegateResult.cs +++ b/src/Polly/DelegateResult.cs @@ -1,32 +1,31 @@ using System; -namespace Polly +namespace Polly; + +/// +/// The captured outcome of executing an individual Func<TResult> +/// +public class DelegateResult { /// - /// The captured outcome of executing an individual Func<TResult> + /// Create an instance of representing an execution which returned /// - public class DelegateResult - { - /// - /// Create an instance of representing an execution which returned - /// - /// The result. - public DelegateResult(TResult result) => Result = result; + /// The result. + public DelegateResult(TResult result) => Result = result; - /// - /// Create an instance of representing an execution which threw - /// - /// The exception. - public DelegateResult(Exception exception) => Exception = exception; + /// + /// Create an instance of representing an execution which threw + /// + /// The exception. + public DelegateResult(Exception exception) => Exception = exception; - /// - /// The result of executing the delegate. Will be default(TResult) if an exception was thrown. - /// - public TResult Result { get; } + /// + /// The result of executing the delegate. Will be default(TResult) if an exception was thrown. + /// + public TResult Result { get; } - /// - /// Any exception thrown while executing the delegate. Will be null if policy executed without exception. - /// - public Exception Exception { get; } - } + /// + /// Any exception thrown while executing the delegate. Will be null if policy executed without exception. + /// + public Exception Exception { get; } } diff --git a/src/Polly/ExceptionPredicate.cs b/src/Polly/ExceptionPredicate.cs index 03cc9dfd4d8..810e98440d0 100644 --- a/src/Polly/ExceptionPredicate.cs +++ b/src/Polly/ExceptionPredicate.cs @@ -1,11 +1,10 @@ using System; -namespace Polly -{ - /// - /// A predicate that can be run against a passed . - /// - /// The passed exception, against which to evaluate the predicate. - /// A matched ; or null, if an exception was not matched. ExceptionPredicate implementations may return the passed Exception , indicating that it matched the predicate. They may also return inner exceptions of the passed Exception , to indicate that the returned inner exception matched the predicate. - public delegate Exception ExceptionPredicate(Exception ex); -} \ No newline at end of file +namespace Polly; + +/// +/// A predicate that can be run against a passed . +/// +/// The passed exception, against which to evaluate the predicate. +/// A matched ; or null, if an exception was not matched. ExceptionPredicate implementations may return the passed Exception , indicating that it matched the predicate. They may also return inner exceptions of the passed Exception , to indicate that the returned inner exception matched the predicate. +public delegate Exception ExceptionPredicate(Exception ex); diff --git a/src/Polly/ExceptionPredicates.cs b/src/Polly/ExceptionPredicates.cs index 2e81b43efec..964c6a9e810 100644 --- a/src/Polly/ExceptionPredicates.cs +++ b/src/Polly/ExceptionPredicates.cs @@ -2,34 +2,33 @@ using System.Collections.Generic; using System.Linq; -namespace Polly +namespace Polly; + +/// +/// A collection of predicates used to define whether a policy handles a given . +/// +public class ExceptionPredicates { - /// - /// A collection of predicates used to define whether a policy handles a given . - /// - public class ExceptionPredicates - { - private List _predicates; + private List _predicates; - internal void Add(ExceptionPredicate predicate) - { - _predicates = _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. + internal void Add(ExceptionPredicate predicate) + { + _predicates = _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.Add(predicate); - } + _predicates.Add(predicate); + } - /// - /// Assess whether the passed , , matches any of the predicates. - /// If the .HandleInner() method was used when configuring the policy, predicates may test whether any inner exceptions of match and may return a matching inner exception. - /// - /// The exception to assess against the predicates. - /// The first exception to match a predicate; or null, if no match is found. - public Exception FirstMatchOrDefault(Exception ex) => _predicates?.Select(predicate => predicate(ex)).FirstOrDefault(e => e != null); + /// + /// Assess whether the passed , , matches any of the predicates. + /// If the .HandleInner() method was used when configuring the policy, predicates may test whether any inner exceptions of match and may return a matching inner exception. + /// + /// The exception to assess against the predicates. + /// The first exception to match a predicate; or null, if no match is found. + public Exception FirstMatchOrDefault(Exception ex) => _predicates?.Select(predicate => predicate(ex)).FirstOrDefault(e => e != null); - /// - /// Specifies that no Exception-handling filters are applied or are required. - /// - public static readonly ExceptionPredicates None = new ExceptionPredicates(); - } + /// + /// Specifies that no Exception-handling filters are applied or are required. + /// + public static readonly ExceptionPredicates None = new ExceptionPredicates(); +} -} \ No newline at end of file diff --git a/src/Polly/ExecutionRejectedException.cs b/src/Polly/ExecutionRejectedException.cs index e756f176496..0d9c0dd37e8 100644 --- a/src/Polly/ExecutionRejectedException.cs +++ b/src/Polly/ExecutionRejectedException.cs @@ -3,49 +3,48 @@ using System.Runtime.Serialization; #endif -namespace Polly +namespace Polly; + +/// +/// Exception thrown when a policy rejects execution of a delegate. +/// More specific exceptions which derive from this type, are generally thrown. +/// +public abstract class ExecutionRejectedException : Exception { /// - /// Exception thrown when a policy rejects execution of a delegate. - /// More specific exceptions which derive from this type, are generally thrown. + /// Initializes a new instance of the class. /// - public abstract class ExecutionRejectedException : Exception + protected ExecutionRejectedException() { - /// - /// Initializes a new instance of the class. - /// - protected ExecutionRejectedException() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - protected ExecutionRejectedException(string message) : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + protected ExecutionRejectedException(string message) : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - /// The inner exception. - protected ExecutionRejectedException(string message, Exception inner) : base(message, inner) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The inner exception. + protected ExecutionRejectedException(string message, Exception inner) : base(message, inner) + { + } #if NETSTANDARD2_0 - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected ExecutionRejectedException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected ExecutionRejectedException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { } +#endif } diff --git a/src/Polly/Fallback/AsyncFallbackEngine.cs b/src/Polly/Fallback/AsyncFallbackEngine.cs index c8a256d1976..29f27e85f08 100644 --- a/src/Polly/Fallback/AsyncFallbackEngine.cs +++ b/src/Polly/Fallback/AsyncFallbackEngine.cs @@ -2,49 +2,48 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Fallback +namespace Polly.Fallback; + +internal class AsyncFallbackEngine { - internal class AsyncFallbackEngine + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Func, Context, Task> onFallbackAsync, + Func, Context, CancellationToken, Task> fallbackAction, + bool continueOnCapturedContext) { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Func, Context, Task> onFallbackAsync, - Func, Context, CancellationToken, Task> fallbackAction, - bool continueOnCapturedContext) - { - DelegateResult delegateOutcome; - - try - { - cancellationToken.ThrowIfCancellationRequested(); + DelegateResult delegateOutcome; - TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + try + { + cancellationToken.ThrowIfCancellationRequested(); - if (!shouldHandleResultPredicates.AnyMatch(result)) - { - return result; - } + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - delegateOutcome = new DelegateResult(result); - } - catch (Exception ex) + if (!shouldHandleResultPredicates.AnyMatch(result)) { - Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } - - delegateOutcome = new DelegateResult(handledException); + return result; } - await onFallbackAsync(delegateOutcome, context).ConfigureAwait(continueOnCapturedContext); + delegateOutcome = new DelegateResult(result); + } + catch (Exception ex) + { + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - return await fallbackAction(delegateOutcome, context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + delegateOutcome = new DelegateResult(handledException); } + + await onFallbackAsync(delegateOutcome, context).ConfigureAwait(continueOnCapturedContext); + + return await fallbackAction(delegateOutcome, context, cancellationToken).ConfigureAwait(continueOnCapturedContext); } } diff --git a/src/Polly/Fallback/AsyncFallbackPolicy.cs b/src/Polly/Fallback/AsyncFallbackPolicy.cs index 11e572a80b4..974cddba326 100644 --- a/src/Polly/Fallback/AsyncFallbackPolicy.cs +++ b/src/Polly/Fallback/AsyncFallbackPolicy.cs @@ -4,84 +4,83 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Fallback -{ - /// - /// A fallback policy that can be applied to asynchronous delegates. - /// - public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy - { - private Func _onFallbackAsync; - private Func _fallbackAction; - - internal AsyncFallbackPolicy(PolicyBuilder policyBuilder, Func onFallbackAsync, - Func fallbackAction) - : base(policyBuilder) - { - _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } +namespace Polly.Fallback; - /// - protected override Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncFallbackEngine.ImplementationAsync( - async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, ctx) => _onFallbackAsync(outcome.Exception, ctx), - async (outcome, ctx, ct) => - { - await _fallbackAction(outcome.Exception, ctx, ct).ConfigureAwait(continueOnCapturedContext); - return EmptyStruct.Instance; - }, - continueOnCapturedContext); - } +/// +/// A fallback policy that can be applied to asynchronous delegates. +/// +public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy +{ + private Func _onFallbackAsync; + private Func _fallbackAction; - /// - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => - throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); + internal AsyncFallbackPolicy(PolicyBuilder policyBuilder, Func onFallbackAsync, + Func fallbackAction) + : base(policyBuilder) + { + _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); } - /// - /// A fallback policy that can be applied to delegates. - /// - /// The return type of delegates which may be executed through the policy. - public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy + /// + protected override Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) { - private Func, Context, Task> _onFallbackAsync; - private Func, Context, CancellationToken, Task> _fallbackAction; + return AsyncFallbackEngine.ImplementationAsync( + async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + (outcome, ctx) => _onFallbackAsync(outcome.Exception, ctx), + async (outcome, ctx, ct) => + { + await _fallbackAction(outcome.Exception, ctx, ct).ConfigureAwait(continueOnCapturedContext); + return EmptyStruct.Instance; + }, + continueOnCapturedContext); + } + + /// + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => + throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); +} - internal AsyncFallbackPolicy( - PolicyBuilder policyBuilder, - Func, Context, Task> onFallbackAsync, - Func, Context, CancellationToken, Task> fallbackAction - ) : base(policyBuilder) - { - _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } +/// +/// A fallback policy that can be applied to delegates. +/// +/// The return type of delegates which may be executed through the policy. +public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy +{ + private Func, Context, Task> _onFallbackAsync; + private Func, Context, CancellationToken, Task> _fallbackAction; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncFallbackEngine.ImplementationAsync( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _onFallbackAsync, - _fallbackAction, - continueOnCapturedContext); + internal AsyncFallbackPolicy( + PolicyBuilder policyBuilder, + Func, Context, Task> onFallbackAsync, + Func, Context, CancellationToken, Task> fallbackAction + ) : base(policyBuilder) + { + _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); } -} \ No newline at end of file + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncFallbackEngine.ImplementationAsync( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onFallbackAsync, + _fallbackAction, + continueOnCapturedContext); +} diff --git a/src/Polly/Fallback/AsyncFallbackSyntax.cs b/src/Polly/Fallback/AsyncFallbackSyntax.cs index 338e34f3060..04749012a11 100644 --- a/src/Polly/Fallback/AsyncFallbackSyntax.cs +++ b/src/Polly/Fallback/AsyncFallbackSyntax.cs @@ -4,215 +4,214 @@ using Polly.Fallback; using Polly.Utilities; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Fallback . +/// +public static class AsyncFallbackSyntax { /// - /// Fluent API for defining a Fallback . + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, asynchronously calls . + /// + /// The policy builder. + /// The fallback delegate. + /// fallbackAction + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Func doNothing = _ => TaskHelper.EmptyTask; + return policyBuilder.FallbackAsync( + fallbackAction, + doNothing + ); + } + + /// + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception; then asynchronously calls . /// - public static class AsyncFallbackSyntax + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) { - /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, asynchronously calls . - /// - /// The policy builder. - /// The fallback delegate. - /// fallbackAction - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Func doNothing = _ => TaskHelper.EmptyTask; - return policyBuilder.FallbackAsync( - fallbackAction, - doNothing - ); - } - - /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception; then asynchronously calls . - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, ct) => fallbackAction(ct), - (outcome, _) => onFallbackAsync(outcome) - ); - } - - /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); - } - - /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return new AsyncFallbackPolicy(policyBuilder, onFallbackAsync, fallbackAction); - } + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, ct) => fallbackAction(ct), + (outcome, _) => onFallbackAsync(outcome) + ); } /// - /// Fluent API for defining an async Fallback policy governing executions returning TResult. + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . /// - public static class AsyncFallbackTResultSyntax + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) { - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue) - { - Func, Task> doNothing = _ => TaskHelper.EmptyTask; - return policyBuilder.FallbackAsync( - _ => Task.FromResult(fallbackValue), - doNothing - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// fallbackAction - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Func, Task> doNothing = _ => TaskHelper.EmptyTask; - return policyBuilder.FallbackAsync( - fallbackAction, - doNothing - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call asynchronously before invoking the fallback delegate. - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Task> onFallbackAsync) - { - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, _) => Task.FromResult(fallbackValue), - (outcome, _) => onFallbackAsync(outcome) - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Task> onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, ct) => fallbackAction(ct), - (outcome, _) => onFallbackAsync(outcome) - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call asynchronously before invoking the fallback delegate. - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Context, Task> onFallbackAsync) - { - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, _) => Task.FromResult(fallbackValue), - onFallbackAsync - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Context, Task> onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, Task> fallbackAction, Func, Context, Task> onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return new AsyncFallbackPolicy( - policyBuilder, - onFallbackAsync, - fallbackAction); - } + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); + } + + /// + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return new AsyncFallbackPolicy(policyBuilder, onFallbackAsync, fallbackAction); + } +} + +/// +/// Fluent API for defining an async Fallback policy governing executions returning TResult. +/// +public static class AsyncFallbackTResultSyntax +{ + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue) + { + Func, Task> doNothing = _ => TaskHelper.EmptyTask; + return policyBuilder.FallbackAsync( + _ => Task.FromResult(fallbackValue), + doNothing + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// fallbackAction + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Func, Task> doNothing = _ => TaskHelper.EmptyTask; + return policyBuilder.FallbackAsync( + fallbackAction, + doNothing + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call asynchronously before invoking the fallback delegate. + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Task> onFallbackAsync) + { + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, _) => Task.FromResult(fallbackValue), + (outcome, _) => onFallbackAsync(outcome) + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Task> onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, ct) => fallbackAction(ct), + (outcome, _) => onFallbackAsync(outcome) + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call asynchronously before invoking the fallback delegate. + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Context, Task> onFallbackAsync) + { + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, _) => Task.FromResult(fallbackValue), + onFallbackAsync + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Context, Task> onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, Task> fallbackAction, Func, Context, Task> onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return new AsyncFallbackPolicy( + policyBuilder, + onFallbackAsync, + fallbackAction); } -} \ No newline at end of file +} diff --git a/src/Polly/Fallback/FallbackEngine.cs b/src/Polly/Fallback/FallbackEngine.cs index eae49ff82ca..2e7f4f4762c 100644 --- a/src/Polly/Fallback/FallbackEngine.cs +++ b/src/Polly/Fallback/FallbackEngine.cs @@ -1,48 +1,47 @@ using System; using System.Threading; -namespace Polly.Fallback +namespace Polly.Fallback; + +internal static class FallbackEngine { - internal static class FallbackEngine + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Action, Context> onFallback, + Func, Context, CancellationToken, TResult> fallbackAction) { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Action, Context> onFallback, - Func, Context, CancellationToken, TResult> fallbackAction) - { - DelegateResult delegateOutcome; - - try - { - cancellationToken.ThrowIfCancellationRequested(); + DelegateResult delegateOutcome; - TResult result = action(context, cancellationToken); + try + { + cancellationToken.ThrowIfCancellationRequested(); - if (!shouldHandleResultPredicates.AnyMatch(result)) - { - return result; - } + TResult result = action(context, cancellationToken); - delegateOutcome = new DelegateResult(result); - } - catch (Exception ex) + if (!shouldHandleResultPredicates.AnyMatch(result)) { - Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } - - delegateOutcome = new DelegateResult(handledException); + return result; } - onFallback(delegateOutcome, context); + delegateOutcome = new DelegateResult(result); + } + catch (Exception ex) + { + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - return fallbackAction(delegateOutcome, context, cancellationToken); + delegateOutcome = new DelegateResult(handledException); } + + onFallback(delegateOutcome, context); + + return fallbackAction(delegateOutcome, context, cancellationToken); } } diff --git a/src/Polly/Fallback/FallbackPolicy.cs b/src/Polly/Fallback/FallbackPolicy.cs index b5dfb047b0c..8933a8c6366 100644 --- a/src/Polly/Fallback/FallbackPolicy.cs +++ b/src/Polly/Fallback/FallbackPolicy.cs @@ -3,71 +3,70 @@ using System.Threading; using Polly.Utilities; -namespace Polly.Fallback +namespace Polly.Fallback; + +/// +/// A fallback policy that can be applied to delegates. +/// +public class FallbackPolicy : Policy, IFallbackPolicy { - /// - /// A fallback policy that can be applied to delegates. - /// - public class FallbackPolicy : Policy, IFallbackPolicy + private Action _onFallback; + private Action _fallbackAction; + + internal FallbackPolicy( + PolicyBuilder policyBuilder, + Action onFallback, + Action fallbackAction) + : base(policyBuilder) { - private Action _onFallback; - private Action _fallbackAction; + _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); + } - internal FallbackPolicy( - PolicyBuilder policyBuilder, - Action onFallback, - Action fallbackAction) - : base(policyBuilder) - { - _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } + /// + [DebuggerStepThrough] + protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) + => FallbackEngine.Implementation( + (ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + (outcome, ctx) => _onFallback(outcome.Exception, ctx), + (outcome, ctx, ct) => { _fallbackAction(outcome.Exception, ctx, ct); return EmptyStruct.Instance; }); - /// - [DebuggerStepThrough] - protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) - => FallbackEngine.Implementation( - (ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, ctx) => _onFallback(outcome.Exception, ctx), - (outcome, ctx, ct) => { _fallbackAction(outcome.Exception, ctx, ct); return EmptyStruct.Instance; }); + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); +} - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); - } +/// +/// A fallback policy that can be applied to delegates returning a value of type . +/// +public class FallbackPolicy : Policy, IFallbackPolicy +{ + private Action, Context> _onFallback; + private Func, Context, CancellationToken, TResult> _fallbackAction; - /// - /// A fallback policy that can be applied to delegates returning a value of type . - /// - public class FallbackPolicy : Policy, IFallbackPolicy + internal FallbackPolicy( + PolicyBuilder policyBuilder, + Action, Context> onFallback, + Func, Context, CancellationToken, TResult> fallbackAction + ) : base(policyBuilder) { - private Action, Context> _onFallback; - private Func, Context, CancellationToken, TResult> _fallbackAction; - - internal FallbackPolicy( - PolicyBuilder policyBuilder, - Action, Context> onFallback, - Func, Context, CancellationToken, TResult> fallbackAction - ) : base(policyBuilder) - { - _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => FallbackEngine.Implementation( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _onFallback, - _fallbackAction); + _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); } -} \ No newline at end of file + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => FallbackEngine.Implementation( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onFallback, + _fallbackAction); +} diff --git a/src/Polly/Fallback/FallbackSyntax.cs b/src/Polly/Fallback/FallbackSyntax.cs index 708e72250b1..0120ba74ff2 100644 --- a/src/Polly/Fallback/FallbackSyntax.cs +++ b/src/Polly/Fallback/FallbackSyntax.cs @@ -2,296 +2,295 @@ using System.Threading; using Polly.Fallback; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Fallback policy. +/// +public static class FallbackSyntax { /// - /// Fluent API for defining a Fallback policy. + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackAction(), (exception, _) => onFallback(exception)); + } + + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (exception, _) => onFallback(exception)); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . /// - public static class FallbackSyntax + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) { - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackAction(), (exception, _) => onFallback(exception)); - } - - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (exception, _) => onFallback(exception)); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return new FallbackPolicy( - policyBuilder, - onFallback, - fallbackAction); - } + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); } /// - /// Fluent API for defining a Fallback policy governing executions returning TResult. + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . /// - public static class FallbackTResultSyntax + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) { - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, returns instead. - /// - /// The policy builder. - /// The fallback value to provide. - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue) - { - Action> doNothing = _ => { }; - return policyBuilder.Fallback(() => fallbackValue, doNothing); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action> doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action> doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call before invoking the fallback delegate. - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action> onFallback) - { - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackValue, (outcome, _) => onFallback(outcome)); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackAction(), (outcome, _) => onFallback(outcome)); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (outcome, _) => onFallback(outcome)); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call before invoking the fallback delegate. - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action, Context> onFallback) - { - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackValue, onFallback); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, TResult> fallbackAction, Action, Context> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return new FallbackPolicy( + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return new FallbackPolicy( policyBuilder, onFallback, fallbackAction); - } + } +} + +/// +/// Fluent API for defining a Fallback policy governing executions returning TResult. +/// +public static class FallbackTResultSyntax +{ + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, returns instead. + /// + /// The policy builder. + /// The fallback value to provide. + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue) + { + Action> doNothing = _ => { }; + return policyBuilder.Fallback(() => fallbackValue, doNothing); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action> doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action> doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call before invoking the fallback delegate. + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action> onFallback) + { + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackValue, (outcome, _) => onFallback(outcome)); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackAction(), (outcome, _) => onFallback(outcome)); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (outcome, _) => onFallback(outcome)); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call before invoking the fallback delegate. + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action, Context> onFallback) + { + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackValue, onFallback); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, TResult> fallbackAction, Action, Context> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return new FallbackPolicy( + policyBuilder, + onFallback, + fallbackAction); } } diff --git a/src/Polly/Fallback/IFallbackPolicy.cs b/src/Polly/Fallback/IFallbackPolicy.cs index 894e2590cc6..ee908574a41 100644 --- a/src/Polly/Fallback/IFallbackPolicy.cs +++ b/src/Polly/Fallback/IFallbackPolicy.cs @@ -1,17 +1,16 @@ -namespace Polly.Fallback -{ - /// - /// Defines properties and methods common to all Fallback policies. - /// +namespace Polly.Fallback; - public interface IFallbackPolicy : IsPolicy - { - } +/// +/// Defines properties and methods common to all Fallback policies. +/// - /// - /// Defines properties and methods common to all Fallback policies generic-typed for executions returning results of type . - /// - public interface IFallbackPolicy : IFallbackPolicy - { - } +public interface IFallbackPolicy : IsPolicy +{ +} + +/// +/// Defines properties and methods common to all Fallback policies generic-typed for executions returning results of type . +/// +public interface IFallbackPolicy : IFallbackPolicy +{ } diff --git a/src/Polly/IAsyncPolicy.Extensions.cs b/src/Polly/IAsyncPolicy.Extensions.cs index 92f8fc22911..643f2b3fe5e 100644 --- a/src/Polly/IAsyncPolicy.Extensions.cs +++ b/src/Polly/IAsyncPolicy.Extensions.cs @@ -1,18 +1,17 @@ -namespace Polly +namespace Polly; + +/// +/// Contains extensions methods on +/// +public static class IAsyncPolicyExtensions { /// - /// Contains extensions methods on + /// Converts a non-generic into a generic for handling only executions returning . /// - public static class IAsyncPolicyExtensions - { - /// - /// Converts a non-generic into a generic for handling only executions returning . - /// - /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . - /// The non-generic to convert to a generic . - /// A generic version of the supplied non-generic . - public static IAsyncPolicy AsAsyncPolicy(this IAsyncPolicy policy) - => policy.WrapAsync(Policy.NoOpAsync()); - } - + /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . + /// The non-generic to convert to a generic . + /// A generic version of the supplied non-generic . + public static IAsyncPolicy AsAsyncPolicy(this IAsyncPolicy policy) + => policy.WrapAsync(Policy.NoOpAsync()); } + diff --git a/src/Polly/IAsyncPolicy.TResult.cs b/src/Polly/IAsyncPolicy.TResult.cs index efc8ffab022..9b3b120e4f8 100644 --- a/src/Polly/IAsyncPolicy.TResult.cs +++ b/src/Polly/IAsyncPolicy.TResult.cs @@ -3,183 +3,182 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +/// +/// An interface defining all executions available on an asynchronous policy generic-typed for executions returning results of type . +/// +/// The type of the result of funcs executed through the Policy. +public interface IAsyncPolicy : IsPolicy { /// - /// An interface defining all executions available on an asynchronous policy generic-typed for executions returning results of type . - /// - /// The type of the result of funcs executed through the Policy. - public interface IAsyncPolicy : IsPolicy - { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy WithPolicyKey(string policyKey); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The value returned by the action - Task ExecuteAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - } + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy WithPolicyKey(string policyKey); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The value returned by the action + Task ExecuteAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); } diff --git a/src/Polly/IAsyncPolicy.cs b/src/Polly/IAsyncPolicy.cs index 34da99e71cc..c93424cea73 100644 --- a/src/Polly/IAsyncPolicy.cs +++ b/src/Polly/IAsyncPolicy.cs @@ -3,347 +3,346 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +/// +/// An interface defining all executions available on a non-generic, asynchronous policy +/// +public interface IAsyncPolicy : IsPolicy { /// - /// An interface defining all executions available on a non-generic, asynchronous policy - /// - public interface IAsyncPolicy : IsPolicy - { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy WithPolicyKey(string policyKey); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - Task ExecuteAsync(Func action); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - Task ExecuteAsync(Func action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - Task ExecuteAsync(Func action, Context context); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAsync(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The value returned by the action - Task ExecuteAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// The captured result - Task ExecuteAndCaptureAsync(Func action); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - Task ExecuteAndCaptureAsync(Func action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - Task ExecuteAndCaptureAsync(Func action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - /// The captured result - Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - } + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy WithPolicyKey(string policyKey); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + Task ExecuteAsync(Func action); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + Task ExecuteAsync(Func action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + Task ExecuteAsync(Func action, Context context); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAsync(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The value returned by the action + Task ExecuteAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// The captured result + Task ExecuteAndCaptureAsync(Func action); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + Task ExecuteAndCaptureAsync(Func action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + Task ExecuteAndCaptureAsync(Func action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + /// The captured result + Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); } diff --git a/src/Polly/ISyncPolicy.Extensions.cs b/src/Polly/ISyncPolicy.Extensions.cs index af4d41766f0..163be436370 100644 --- a/src/Polly/ISyncPolicy.Extensions.cs +++ b/src/Polly/ISyncPolicy.Extensions.cs @@ -1,18 +1,17 @@ -namespace Polly +namespace Polly; + +/// +/// Contains extensions methods on +/// +public static class ISyncPolicyExtensions { /// - /// Contains extensions methods on + /// Converts a non-generic into a generic for handling only executions returning . /// - public static class ISyncPolicyExtensions - { - /// - /// Converts a non-generic into a generic for handling only executions returning . - /// - /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . - /// The non-generic to convert to a generic . - /// A generic version of the supplied non-generic . - public static ISyncPolicy AsPolicy(this ISyncPolicy policy) - => policy.Wrap(Policy.NoOp()); - } - + /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . + /// The non-generic to convert to a generic . + /// A generic version of the supplied non-generic . + public static ISyncPolicy AsPolicy(this ISyncPolicy policy) + => policy.Wrap(Policy.NoOp()); } + diff --git a/src/Polly/ISyncPolicy.TResult.cs b/src/Polly/ISyncPolicy.TResult.cs index 6a8cb85c110..5c851dce096 100644 --- a/src/Polly/ISyncPolicy.TResult.cs +++ b/src/Polly/ISyncPolicy.TResult.cs @@ -2,130 +2,129 @@ using System.Collections.Generic; using System.Threading; -namespace Polly +namespace Polly; + +/// +/// An interface defining all executions available on a synchronous policy generic-typed for executions returning results of type . +/// +/// The type of the result of funcs executed through the Policy. + +public interface ISyncPolicy : IsPolicy { /// - /// An interface defining all executions available on a synchronous policy generic-typed for executions returning results of type . + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. /// - /// The type of the result of funcs executed through the Policy. + /// The unique, used-definable key to assign to this instance. + ISyncPolicy WithPolicyKey(string policyKey); - public interface ISyncPolicy : IsPolicy - { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy WithPolicyKey(string policyKey); - - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The action to perform. - /// The value returned by the action - TResult Execute(Func action); + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The action to perform. + /// The value returned by the action + TResult Execute(Func action); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, IDictionary contextData); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, IDictionary contextData); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// context - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, Context context); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// context + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, Context context); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, Context context, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, Context context, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - PolicyResult ExecuteAndCapture(Func action); + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + PolicyResult ExecuteAndCapture(Func action); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context); + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context); - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); - } + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); } diff --git a/src/Polly/ISyncPolicy.cs b/src/Polly/ISyncPolicy.cs index bbb750fa29b..14dee2b9da2 100644 --- a/src/Polly/ISyncPolicy.cs +++ b/src/Polly/ISyncPolicy.cs @@ -2,231 +2,230 @@ using System.Collections.Generic; using System.Threading; -namespace Polly +namespace Polly; + +/// +/// An interface defining all executions available on a non-generic, synchronous policy +/// +public interface ISyncPolicy : IsPolicy { /// - /// An interface defining all executions available on a non-generic, synchronous policy - /// - public interface ISyncPolicy : IsPolicy - { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy WithPolicyKey(string policyKey); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - void Execute(Action action); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - void Execute(Action action, IDictionary contextData); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - void Execute(Action action, Context context); - - /// - /// - /// - /// - /// - void Execute(Action action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// contextData - void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - void Execute(Action action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The type of the Result. - /// The action to perform. - /// The value returned by the action - TResult Execute(Func action); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, IDictionary contextData); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, Context context); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - PolicyResult ExecuteAndCapture(Action action); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Action action, IDictionary contextData); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - PolicyResult ExecuteAndCapture(Action action, Context context); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - PolicyResult ExecuteAndCapture(Func action); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The type of the t result. - /// The action to perform. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); - } + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + ISyncPolicy WithPolicyKey(string policyKey); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + void Execute(Action action); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + void Execute(Action action, IDictionary contextData); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + void Execute(Action action, Context context); + + /// + /// + /// + /// + /// + void Execute(Action action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// contextData + void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + void Execute(Action action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The type of the Result. + /// The action to perform. + /// The value returned by the action + TResult Execute(Func action); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, IDictionary contextData); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, Context context); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + PolicyResult ExecuteAndCapture(Action action); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Action action, IDictionary contextData); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + PolicyResult ExecuteAndCapture(Action action, Context context); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + PolicyResult ExecuteAndCapture(Func action); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The type of the t result. + /// The action to perform. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); } diff --git a/src/Polly/IsPolicy.cs b/src/Polly/IsPolicy.cs index ed3211a8983..559d815e1a9 100644 --- a/src/Polly/IsPolicy.cs +++ b/src/Polly/IsPolicy.cs @@ -1,13 +1,12 @@ -namespace Polly -{ +namespace Polly; + +/// +/// A marker interface identifying Polly policies of all types, and containing properties common to all policies +/// +public interface IsPolicy +{ /// - /// A marker interface identifying Polly policies of all types, and containing properties common to all policies + /// A key intended to be unique to each policy instance, which is passed with executions as the property. /// - public interface IsPolicy - { - /// - /// A key intended to be unique to each policy instance, which is passed with executions as the property. - /// - string PolicyKey { get; } - } + string PolicyKey { get; } } diff --git a/src/Polly/NoOp/AsyncNoOpPolicy.cs b/src/Polly/NoOp/AsyncNoOpPolicy.cs index 305f4f2563a..39dbc67b577 100644 --- a/src/Polly/NoOp/AsyncNoOpPolicy.cs +++ b/src/Polly/NoOp/AsyncNoOpPolicy.cs @@ -3,37 +3,36 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.NoOp +namespace Polly.NoOp; + +/// +/// A noop policy that can be applied to asynchronous delegates. +/// +public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy { - /// - /// A noop policy that can be applied to asynchronous delegates. - /// - public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy + internal AsyncNoOpPolicy() { - internal AsyncNoOpPolicy() - { - } - - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); } - /// - /// A noop policy that can be applied to asynchronous delegates returning a value of type . - /// - public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy - { - internal AsyncNoOpPolicy() - { - } + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); +} - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); +/// +/// A noop policy that can be applied to asynchronous delegates returning a value of type . +/// +public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy +{ + internal AsyncNoOpPolicy() + { } -} \ No newline at end of file + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); +} diff --git a/src/Polly/NoOp/AsyncNoOpSyntax.cs b/src/Polly/NoOp/AsyncNoOpSyntax.cs index 3ad872db506..d18a9b091c7 100644 --- a/src/Polly/NoOp/AsyncNoOpSyntax.cs +++ b/src/Polly/NoOp/AsyncNoOpSyntax.cs @@ -1,13 +1,12 @@ using Polly.NoOp; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy - { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The policy instance. - public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); - } + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The policy instance. + public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); } diff --git a/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs b/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs index 508ef723174..bfa65dbea5c 100644 --- a/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs +++ b/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs @@ -1,14 +1,13 @@ using Polly.NoOp; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy - { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The type of return values this policy will handle. - /// The policy instance. - public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); - } + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The type of return values this policy will handle. + /// The policy instance. + public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); } diff --git a/src/Polly/NoOp/INoOpPolicy.cs b/src/Polly/NoOp/INoOpPolicy.cs index b32f4cfe177..82dadfffe12 100644 --- a/src/Polly/NoOp/INoOpPolicy.cs +++ b/src/Polly/NoOp/INoOpPolicy.cs @@ -1,17 +1,16 @@ -namespace Polly.NoOp -{ - /// - /// Defines properties and methods common to all NoOp policies. - /// +namespace Polly.NoOp; - public interface INoOpPolicy : IsPolicy - { - } +/// +/// Defines properties and methods common to all NoOp policies. +/// - /// - /// Defines properties and methods common to all NoOp policies generic-typed for executions returning results of type . - /// - public interface INoOpPolicy : INoOpPolicy - { - } +public interface INoOpPolicy : IsPolicy +{ +} + +/// +/// Defines properties and methods common to all NoOp policies generic-typed for executions returning results of type . +/// +public interface INoOpPolicy : INoOpPolicy +{ } diff --git a/src/Polly/NoOp/NoOpEngine.cs b/src/Polly/NoOp/NoOpEngine.cs index 84c7c450623..403a8c70a76 100644 --- a/src/Polly/NoOp/NoOpEngine.cs +++ b/src/Polly/NoOp/NoOpEngine.cs @@ -1,11 +1,10 @@ using System; using System.Threading; -namespace Polly.NoOp +namespace Polly.NoOp; + +internal static partial class NoOpEngine { - internal static partial class NoOpEngine - { - internal static TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => action(context, cancellationToken); - } + internal static TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => action(context, cancellationToken); } diff --git a/src/Polly/NoOp/NoOpEngineAsync.cs b/src/Polly/NoOp/NoOpEngineAsync.cs index bd4f1a5db6f..1056d0fe9ea 100644 --- a/src/Polly/NoOp/NoOpEngineAsync.cs +++ b/src/Polly/NoOp/NoOpEngineAsync.cs @@ -2,11 +2,10 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.NoOp +namespace Polly.NoOp; + +internal static partial class NoOpEngine { - internal static partial class NoOpEngine - { - internal static async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - => await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + internal static async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + => await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); } diff --git a/src/Polly/NoOp/NoOpPolicy.cs b/src/Polly/NoOp/NoOpPolicy.cs index 655ec0368b7..5bfe9fb57e3 100644 --- a/src/Polly/NoOp/NoOpPolicy.cs +++ b/src/Polly/NoOp/NoOpPolicy.cs @@ -2,36 +2,35 @@ using System.Diagnostics; using System.Threading; -namespace Polly.NoOp +namespace Polly.NoOp; + +/// +/// A no op policy that can be applied to delegates. +/// +public class NoOpPolicy : Policy, INoOpPolicy { - /// - /// A no op policy that can be applied to delegates. - /// - public class NoOpPolicy : Policy, INoOpPolicy + internal NoOpPolicy() { - internal NoOpPolicy() - { - } - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => NoOpEngine.Implementation(action, context, cancellationToken); } - /// - /// A no op policy that can be applied to delegates returning a value of type - /// - /// The type of return values this policy will handle. - public class NoOpPolicy : Policy, INoOpPolicy - { - internal NoOpPolicy() - { - } + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => NoOpEngine.Implementation(action, context, cancellationToken); +} - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => NoOpEngine.Implementation(action, context, cancellationToken); +/// +/// A no op policy that can be applied to delegates returning a value of type +/// +/// The type of return values this policy will handle. +public class NoOpPolicy : Policy, INoOpPolicy +{ + internal NoOpPolicy() + { } + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => NoOpEngine.Implementation(action, context, cancellationToken); } diff --git a/src/Polly/NoOp/NoOpSyntax.cs b/src/Polly/NoOp/NoOpSyntax.cs index 659e893e6bd..149320a0a75 100644 --- a/src/Polly/NoOp/NoOpSyntax.cs +++ b/src/Polly/NoOp/NoOpSyntax.cs @@ -1,13 +1,12 @@ using Polly.NoOp; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy - { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The policy instance. - public static NoOpPolicy NoOp() => new NoOpPolicy(); - } + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The policy instance. + public static NoOpPolicy NoOp() => new NoOpPolicy(); } diff --git a/src/Polly/NoOp/NoOpTResultSyntax.cs b/src/Polly/NoOp/NoOpTResultSyntax.cs index 11bc9eefb54..f5d768a7fef 100644 --- a/src/Polly/NoOp/NoOpTResultSyntax.cs +++ b/src/Polly/NoOp/NoOpTResultSyntax.cs @@ -1,14 +1,13 @@ using Polly.NoOp; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy - { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The type of return values this policy will handle. - /// The policy instance. - public static NoOpPolicy NoOp() => new NoOpPolicy(); - } + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The type of return values this policy will handle. + /// The policy instance. + public static NoOpPolicy NoOp() => new NoOpPolicy(); } diff --git a/src/Polly/Policy.ContextAndKeys.cs b/src/Polly/Policy.ContextAndKeys.cs index bb72a103316..c5b454274ae 100644 --- a/src/Polly/Policy.ContextAndKeys.cs +++ b/src/Polly/Policy.ContextAndKeys.cs @@ -1,60 +1,59 @@ -namespace Polly +namespace Polly; + +public abstract partial class Policy { - public abstract partial class Policy + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public Policy WithPolicyKey(string policyKey) { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public Policy WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } - - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; } - public abstract partial class Policy + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public Policy WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } - - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } +} + +public abstract partial class Policy +{ + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public Policy WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } + + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; } } diff --git a/src/Polly/Policy.ExecuteOverloads.cs b/src/Polly/Policy.ExecuteOverloads.cs index df6ae88239f..734247514ee 100644 --- a/src/Polly/Policy.ExecuteOverloads.cs +++ b/src/Polly/Policy.ExecuteOverloads.cs @@ -3,333 +3,332 @@ using System.Diagnostics; using System.Threading; -namespace Polly +namespace Polly; + +public abstract partial class Policy : ISyncPolicy { - public abstract partial class Policy : ISyncPolicy + #region Execute overloads + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + [DebuggerStepThrough] + public void Execute(Action action) + => Execute((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + [DebuggerStepThrough] + public void Execute(Action action, IDictionary contextData) + => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + [DebuggerStepThrough] + public void Execute(Action action, Context context) + => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy + /// + /// + /// + [DebuggerStepThrough] + public void Execute(Action action, CancellationToken cancellationToken) + => Execute((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// contextData + [DebuggerStepThrough] + public void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken) + => Execute(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + [DebuggerStepThrough] + public void Execute(Action action, Context context, CancellationToken cancellationToken) { - #region Execute overloads - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - [DebuggerStepThrough] - public void Execute(Action action) - => Execute((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - [DebuggerStepThrough] - public void Execute(Action action, IDictionary contextData) - => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - [DebuggerStepThrough] - public void Execute(Action action, Context context) - => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy - /// - /// - /// - [DebuggerStepThrough] - public void Execute(Action action, CancellationToken cancellationToken) - => Execute((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// contextData - [DebuggerStepThrough] - public void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken) - => Execute(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - [DebuggerStepThrough] - public void Execute(Action action, Context context, CancellationToken cancellationToken) + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - - try - { - Implementation(action, context, cancellationToken); - } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } + Implementation(action, context, cancellationToken); } - - #region Overloads method-generic in TResult - - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The type of the Result. - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action) - => Execute((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData) - => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, Context context) - => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, CancellationToken cancellationToken) - => Execute((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) - => Execute(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, Context context, CancellationToken cancellationToken) + finally { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - - try - { - return Implementation(action, context, cancellationToken); - } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); } + } + + #region Overloads method-generic in TResult + + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The type of the Result. + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action) + => Execute((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData) + => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, Context context) + => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, CancellationToken cancellationToken) + => Execute((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) + => Execute(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, Context context, CancellationToken cancellationToken) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - #endregion - - #endregion - - #region ExecuteAndCapture overloads - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action) - => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData) - => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, Context context) - => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken) - => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCapture(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken) + try + { + return Implementation(action, context, cancellationToken); + } + finally { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - Execute(action, context, cancellationToken); - return PolicyResult.Successful(context); - } - catch (Exception exception) - { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); - } + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); } + } - #region Overloads method-generic in TResult - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action) - => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) - => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context) - => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The type of the t result. - /// The action to perform. - /// The cancellation token. - /// The captured result - public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) - => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCapture(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) + #endregion + + #endregion + + #region ExecuteAndCapture overloads + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action) + => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData) + => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, Context context) + => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken) + => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCapture(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + + try + { + Execute(action, context, cancellationToken); + return PolicyResult.Successful(context); + } + catch (Exception exception) { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - return PolicyResult.Successful(Execute(action, context, cancellationToken), context); - } - catch (Exception exception) - { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); - } + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } - #endregion + } - #endregion + #region Overloads method-generic in TResult + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action) + => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) + => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context) + => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The type of the t result. + /// The action to perform. + /// The cancellation token. + /// The captured result + public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) + => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCapture(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + try + { + return PolicyResult.Successful(Execute(action, context, cancellationToken), context); + } + catch (Exception exception) + { + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + } } + #endregion + + #endregion + } diff --git a/src/Polly/Policy.HandleSyntax.cs b/src/Polly/Policy.HandleSyntax.cs index 8128ca0f497..0314e9bb291 100644 --- a/src/Polly/Policy.HandleSyntax.cs +++ b/src/Polly/Policy.HandleSyntax.cs @@ -1,113 +1,112 @@ using System; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy - { - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder Handle() where TException : Exception - => new PolicyBuilder(exception => exception is TException ? exception : null); + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder Handle() where TException : Exception + => new PolicyBuilder(exception => exception is TException ? exception : null); - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner() where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner() where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); - /// - /// Specifies the type of return result that this policy can handle with additional filters on the result. - /// - /// The type of return values this policy will handle. - /// The predicate to filter results this policy will handle. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(Func resultPredicate) - => new PolicyBuilder(resultPredicate); + /// + /// Specifies the type of return result that this policy can handle with additional filters on the result. + /// + /// The type of return values this policy will handle. + /// The predicate to filter results this policy will handle. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(Func resultPredicate) + => new PolicyBuilder(resultPredicate); - /// - /// Specifies the type of return result that this policy can handle, and a result value which the policy will handle. - /// - /// The type of return values this policy will handle. - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(TResult result) - => HandleResult(new Func(r => (r != null && r.Equals(result)) || (r == null && result == null))); - } + /// + /// Specifies the type of return result that this policy can handle, and a result value which the policy will handle. + /// + /// The type of return values this policy will handle. + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(TResult result) + => HandleResult(new Func(r => (r != null && r.Equals(result)) || (r == null && result == null))); +} - public partial class Policy - { - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance. - public static PolicyBuilder Handle() where TException : Exception - => new PolicyBuilder(exception => exception is TException ? exception : null); +public partial class Policy +{ + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance. + public static PolicyBuilder Handle() where TException : Exception + => new PolicyBuilder(exception => exception is TException ? exception : null); - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance. - public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance. + public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner() where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner() where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); - /// - /// Specifies a filter on the return result values that this strongly-typed generic policy will handle. - /// - /// The predicate to filter the results this policy will handle. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(Func resultPredicate) - => new PolicyBuilder(resultPredicate); + /// + /// Specifies a filter on the return result values that this strongly-typed generic policy will handle. + /// + /// The predicate to filter the results this policy will handle. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(Func resultPredicate) + => new PolicyBuilder(resultPredicate); - /// - /// Specifies a return result value which the strongly-typed generic policy will handle. - /// - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(TResult result) - => HandleResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); - } + /// + /// Specifies a return result value which the strongly-typed generic policy will handle. + /// + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(TResult result) + => HandleResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); } diff --git a/src/Polly/Policy.SyncGenericImplementation.cs b/src/Polly/Policy.SyncGenericImplementation.cs index e02cdaf18ad..70b5ca642de 100644 --- a/src/Polly/Policy.SyncGenericImplementation.cs +++ b/src/Polly/Policy.SyncGenericImplementation.cs @@ -1,21 +1,20 @@ using System; using System.Threading; -namespace Polly +namespace Polly; + +public abstract partial class Policy { - public abstract partial class Policy - { - /// - /// Defines the implementation of a policy for synchronous executions returning . - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// A result of the execution. - protected abstract TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken - ); - } + /// + /// Defines the implementation of a policy for synchronous executions returning . + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// A result of the execution. + protected abstract TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken + ); } diff --git a/src/Polly/Policy.SyncNonGenericImplementation.cs b/src/Polly/Policy.SyncNonGenericImplementation.cs index 60a1c46522e..2e315370840 100644 --- a/src/Polly/Policy.SyncNonGenericImplementation.cs +++ b/src/Polly/Policy.SyncNonGenericImplementation.cs @@ -3,28 +3,27 @@ using System.Threading; using Polly.Utilities; -namespace Polly +namespace Polly; + +public abstract partial class Policy { - public abstract partial class Policy - { - /// - /// Defines the implementation of a policy for sync executions with no return value. - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - [DebuggerStepThrough] - protected virtual void Implementation(Action action, Context context, CancellationToken cancellationToken) - => Implementation((ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, context, cancellationToken); + /// + /// Defines the implementation of a policy for sync executions with no return value. + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + [DebuggerStepThrough] + protected virtual void Implementation(Action action, Context context, CancellationToken cancellationToken) + => Implementation((ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, context, cancellationToken); - /// - /// Defines the implementation of a policy for synchronous executions returning . - /// - /// The type returned by synchronous executions through the implementation. - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// A result of the execution. - protected abstract TResult Implementation(Func action, Context context, CancellationToken cancellationToken); - } + /// + /// Defines the implementation of a policy for synchronous executions returning . + /// + /// The type returned by synchronous executions through the implementation. + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// A result of the execution. + protected abstract TResult Implementation(Func action, Context context, CancellationToken cancellationToken); } diff --git a/src/Polly/Policy.TResult.ExecuteOverloads.cs b/src/Polly/Policy.TResult.ExecuteOverloads.cs index 27475426a59..52a33e195fa 100644 --- a/src/Polly/Policy.TResult.ExecuteOverloads.cs +++ b/src/Polly/Policy.TResult.ExecuteOverloads.cs @@ -4,182 +4,181 @@ using System.Threading; -namespace Polly +namespace Polly; + +public abstract partial class Policy : ISyncPolicy { - public abstract partial class Policy : ISyncPolicy - { - #region Execute overloads - - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action) - => Execute((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData) - => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// context - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, Context context) - => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, CancellationToken cancellationToken) - => Execute((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) - => Execute(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, Context context, CancellationToken cancellationToken) - { - if (context == null) throw new ArgumentNullException(nameof(context)); + #region Execute overloads + + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action) + => Execute((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData) + => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// context + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, Context context) + => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, CancellationToken cancellationToken) + => Execute((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) + => Execute(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, Context context, CancellationToken cancellationToken) + { + if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - try - { - return Implementation(action, context, cancellationToken); - } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } + try + { + return Implementation(action, context, cancellationToken); } - - #endregion - - #region ExecuteAndCapture overloads - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action) - => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) - => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context) - => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) - => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCapture(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) + finally { - if (context == null) throw new ArgumentNullException(nameof(context)); + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } + } - try - { - TResult result = Execute(action, context, cancellationToken); + #endregion + + #region ExecuteAndCapture overloads + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action) + => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) + => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context) + => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) + => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCapture(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) + { + if (context == null) throw new ArgumentNullException(nameof(context)); - if (ResultPredicates.AnyMatch(result)) - { - return PolicyResult.Failure(result, context); - } + try + { + TResult result = Execute(action, context, cancellationToken); - return PolicyResult.Successful(result, context); - } - catch (Exception exception) + if (ResultPredicates.AnyMatch(result)) { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + return PolicyResult.Failure(result, context); } - } - #endregion + return PolicyResult.Successful(result, context); + } + catch (Exception exception) + { + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + } } + + #endregion } diff --git a/src/Polly/Policy.TResult.cs b/src/Polly/Policy.TResult.cs index dfe08232912..a6512eaafcd 100644 --- a/src/Polly/Policy.TResult.cs +++ b/src/Polly/Policy.TResult.cs @@ -1,27 +1,26 @@ -namespace Polly +namespace Polly; + +/// +/// Transient fault handling policies that can be applied to delegates returning results of type +/// +public abstract partial class Policy : PolicyBase { /// - /// Transient fault handling policies that can be applied to delegates returning results of type + /// Constructs a new instance of a derived type with the passed and . /// - public abstract partial class Policy : PolicyBase + /// Predicates indicating which exceptions the policy should handle. + /// Predicates indicating which results the policy should handle. + internal Policy(ExceptionPredicates exceptionPredicates, ResultPredicates resultPredicates) + : base(exceptionPredicates, resultPredicates) { - /// - /// Constructs a new instance of a derived type with the passed and . - /// - /// Predicates indicating which exceptions the policy should handle. - /// Predicates indicating which results the policy should handle. - internal Policy(ExceptionPredicates exceptionPredicates, ResultPredicates resultPredicates) - : base(exceptionPredicates, resultPredicates) - { - } + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A indicating which exceptions and results the policy should handle. - protected Policy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { - } + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A indicating which exceptions and results the policy should handle. + protected Policy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { } } diff --git a/src/Polly/Policy.cs b/src/Polly/Policy.cs index 3c4f18d1a28..192d7d8c2e0 100644 --- a/src/Polly/Policy.cs +++ b/src/Polly/Policy.cs @@ -1,26 +1,25 @@ -namespace Polly +namespace Polly; + +/// +/// Transient exception handling policies that can be applied to synchronous delegates +/// +public abstract partial class Policy : PolicyBase { /// - /// Transient exception handling policies that can be applied to synchronous delegates + /// Constructs a new instance of a derived type with the passed . /// - public abstract partial class Policy : PolicyBase + /// Predicates indicating which exceptions the policy should handle. + internal Policy(ExceptionPredicates exceptionPredicates) + : base(exceptionPredicates) { - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// Predicates indicating which exceptions the policy should handle. - internal Policy(ExceptionPredicates exceptionPredicates) - : base(exceptionPredicates) - { - } + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A specifying which exceptions the policy should handle. - protected Policy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { - } + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A specifying which exceptions the policy should handle. + protected Policy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { } -} \ No newline at end of file +} diff --git a/src/Polly/PolicyBase.ContextAndKeys.cs b/src/Polly/PolicyBase.ContextAndKeys.cs index 2f584230ad4..511bbfa37c7 100644 --- a/src/Polly/PolicyBase.ContextAndKeys.cs +++ b/src/Polly/PolicyBase.ContextAndKeys.cs @@ -1,46 +1,45 @@ using System; using Polly.Utilities; -namespace Polly +namespace Polly; + +public abstract partial class PolicyBase { - public abstract partial class PolicyBase - { - /// - /// A key intended to be unique to each instance. - /// - protected string policyKeyInternal; + /// + /// A key intended to be unique to each instance. + /// + protected string policyKeyInternal; - /// - /// A key intended to be unique to each instance, which is passed with executions as the property. - /// - public string PolicyKey => policyKeyInternal ?? (policyKeyInternal = GetType().Name + "-" + KeyHelper.GuidPart()); + /// + /// A key intended to be unique to each instance, which is passed with executions as the property. + /// + public string PolicyKey => policyKeyInternal ?? (policyKeyInternal = GetType().Name + "-" + KeyHelper.GuidPart()); - internal static ArgumentException PolicyKeyMustBeImmutableException => new ArgumentException("PolicyKey cannot be changed once set; or (when using the default value after the PolicyKey property has been accessed.", "policyKey"); + internal static ArgumentException PolicyKeyMustBeImmutableException => new ArgumentException("PolicyKey cannot be changed once set; or (when using the default value after the PolicyKey property has been accessed.", "policyKey"); - /// - /// Updates the execution with context from the executing policy. - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal virtual void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) // priorPolicyWrapKey and priorPolicyKey could be handled as a (string, string) System.ValueTuple return type instead of out parameters, when our minimum supported target supports this. - { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + /// + /// Updates the execution with context from the executing policy. + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal virtual void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) // priorPolicyWrapKey and priorPolicyKey could be handled as a (string, string) System.ValueTuple return type instead of out parameters, when our minimum supported target supports this. + { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - executionContext.PolicyKey = PolicyKey; - } + executionContext.PolicyKey = PolicyKey; + } - /// - /// Restores the supplied keys to the execution . - /// - /// The execution . - /// The prior to execution through this policy. - /// The prior to execution through this policy. - internal void RestorePolicyContext(Context executionContext, string priorPolicyWrapKey, string priorPolicyKey) - { - executionContext.PolicyWrapKey = priorPolicyWrapKey; - executionContext.PolicyKey = priorPolicyKey; - } + /// + /// Restores the supplied keys to the execution . + /// + /// The execution . + /// The prior to execution through this policy. + /// The prior to execution through this policy. + internal void RestorePolicyContext(Context executionContext, string priorPolicyWrapKey, string priorPolicyKey) + { + executionContext.PolicyWrapKey = priorPolicyWrapKey; + executionContext.PolicyKey = priorPolicyKey; } -} \ No newline at end of file +} diff --git a/src/Polly/PolicyBase.cs b/src/Polly/PolicyBase.cs index 689a5be3fad..68ac86dc44a 100644 --- a/src/Polly/PolicyBase.cs +++ b/src/Polly/PolicyBase.cs @@ -1,82 +1,81 @@ using System; using System.Threading; -namespace Polly +namespace Polly; + +/// +/// Implements elements common to both non-generic and generic policies, and sync and async policies. +/// +public abstract partial class PolicyBase { /// - /// Implements elements common to both non-generic and generic policies, and sync and async policies. + /// Predicates indicating which exceptions the policy handles. /// - public abstract partial class PolicyBase - { - /// - /// Predicates indicating which exceptions the policy handles. - /// - protected internal ExceptionPredicates ExceptionPredicates { get; } + protected internal ExceptionPredicates ExceptionPredicates { get; } - /// - /// Defines a CancellationToken to use, when none is supplied. - /// - internal readonly CancellationToken DefaultCancellationToken = CancellationToken.None; + /// + /// Defines a CancellationToken to use, when none is supplied. + /// + internal readonly CancellationToken DefaultCancellationToken = CancellationToken.None; - /// - /// Defines a value to use for continueOnCaptureContext, when none is supplied. - /// - internal const bool DefaultContinueOnCapturedContext = false; + /// + /// Defines a value to use for continueOnCaptureContext, when none is supplied. + /// + internal const bool DefaultContinueOnCapturedContext = false; - internal static ExceptionType GetExceptionType(ExceptionPredicates exceptionPredicates, Exception exception) - { - bool isExceptionTypeHandledByThisPolicy = exceptionPredicates.FirstMatchOrDefault(exception) != null; + internal static ExceptionType GetExceptionType(ExceptionPredicates exceptionPredicates, Exception exception) + { + bool isExceptionTypeHandledByThisPolicy = exceptionPredicates.FirstMatchOrDefault(exception) != null; - return isExceptionTypeHandledByThisPolicy - ? ExceptionType.HandledByThisPolicy - : ExceptionType.Unhandled; - } + return isExceptionTypeHandledByThisPolicy + ? ExceptionType.HandledByThisPolicy + : ExceptionType.Unhandled; + } - /// - /// Constructs a new instance of a derived type of with the passed . - /// - /// Predicates indicating which exceptions the policy should handle. - internal PolicyBase(ExceptionPredicates exceptionPredicates) - => ExceptionPredicates = exceptionPredicates ?? ExceptionPredicates.None; + /// + /// Constructs a new instance of a derived type of with the passed . + /// + /// Predicates indicating which exceptions the policy should handle. + internal PolicyBase(ExceptionPredicates exceptionPredicates) + => ExceptionPredicates = exceptionPredicates ?? ExceptionPredicates.None; - /// - /// Constructs a new instance of a derived type of with the passed . - /// - /// A indicating which exceptions the policy should handle. - protected PolicyBase(PolicyBuilder policyBuilder) - : this(policyBuilder?.ExceptionPredicates) - { - } + /// + /// Constructs a new instance of a derived type of with the passed . + /// + /// A indicating which exceptions the policy should handle. + protected PolicyBase(PolicyBuilder policyBuilder) + : this(policyBuilder?.ExceptionPredicates) + { } +} +/// +/// Implements elements common to sync and async generic policies. +/// +public abstract class PolicyBase : PolicyBase +{ /// - /// Implements elements common to sync and async generic policies. + /// Predicates indicating which results the policy handles. /// - public abstract class PolicyBase : PolicyBase - { - /// - /// Predicates indicating which results the policy handles. - /// - protected internal ResultPredicates ResultPredicates { get; } + protected internal ResultPredicates ResultPredicates { get; } - /// - /// Constructs a new instance of a derived type of . - /// - /// Predicates indicating which exceptions the policy should handle. - /// Predicates indicating which results the policy should handle. - internal PolicyBase( - ExceptionPredicates exceptionPredicates, - ResultPredicates resultPredicates) - : base(exceptionPredicates) - => ResultPredicates = resultPredicates ?? ResultPredicates.None; + /// + /// Constructs a new instance of a derived type of . + /// + /// Predicates indicating which exceptions the policy should handle. + /// Predicates indicating which results the policy should handle. + internal PolicyBase( + ExceptionPredicates exceptionPredicates, + ResultPredicates resultPredicates) + : base(exceptionPredicates) + => ResultPredicates = resultPredicates ?? ResultPredicates.None; - /// - /// Constructs a new instance of a derived type of with the passed . - /// - /// A indicating which exceptions the policy should handle. - protected PolicyBase(PolicyBuilder policyBuilder) - : this(policyBuilder?.ExceptionPredicates, policyBuilder?.ResultPredicates) - { - } + /// + /// Constructs a new instance of a derived type of with the passed . + /// + /// A indicating which exceptions the policy should handle. + protected PolicyBase(PolicyBuilder policyBuilder) + : this(policyBuilder?.ExceptionPredicates, policyBuilder?.ResultPredicates) + { } } diff --git a/src/Polly/PolicyBuilder.OrSyntax.cs b/src/Polly/PolicyBuilder.OrSyntax.cs index 36fe5c83792..31f5347a665 100644 --- a/src/Polly/PolicyBuilder.OrSyntax.cs +++ b/src/Polly/PolicyBuilder.OrSyntax.cs @@ -1,182 +1,181 @@ using System; -namespace Polly +namespace Polly; + +public partial class PolicyBuilder { - public partial class PolicyBuilder + #region Add exception predicates to exception-filtering policy + + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or() where TException : Exception { - #region Add exception predicates to exception-filtering policy - - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or() where TException : Exception - { - ExceptionPredicates.Add(exception => exception is TException ? exception : null); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception - { - ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner() where TException : Exception - { - ExceptionPredicates.Add((HandleInner(ex => ex is TException))); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception - { - ExceptionPredicates.Add(HandleInner(exception => exception is TException texception && exceptionPredicate(texception))); - return this; - } + ExceptionPredicates.Add(exception => exception is TException ? exception : null); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner() where TException : Exception + { + ExceptionPredicates.Add((HandleInner(ex => ex is TException))); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(HandleInner(exception => exception is TException texception && exceptionPredicate(texception))); + return this; + } - internal static ExceptionPredicate HandleInner(Func predicate) + internal static ExceptionPredicate HandleInner(Func predicate) + { + return exception => { - return exception => + if (exception is AggregateException aggregateException) { - if (exception is AggregateException aggregateException) + //search all inner exceptions wrapped inside the AggregateException recursively + foreach (var innerException in aggregateException.Flatten().InnerExceptions) { - //search all inner exceptions wrapped inside the AggregateException recursively - foreach (var innerException in aggregateException.Flatten().InnerExceptions) - { - var matchedInAggregate = HandleInnerNested(predicate, innerException); - if (matchedInAggregate != null) - return matchedInAggregate; - } + var matchedInAggregate = HandleInnerNested(predicate, innerException); + if (matchedInAggregate != null) + return matchedInAggregate; } + } - return HandleInnerNested(predicate, exception); - }; - } + return HandleInnerNested(predicate, exception); + }; + } - private static Exception HandleInnerNested(Func predicate, Exception current) - { - if (current == null) return null; - if (predicate(current)) return current; - return HandleInnerNested(predicate, current.InnerException); - } - - #endregion - - #region Add result predicates to exception-filtering policy - - /// - /// Specifies the type of result that this policy can handle with additional filters on the result. - /// - /// The type of return values this policy will handle. - /// The predicate to filter the results this policy will handle. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(Func resultPredicate) - => new PolicyBuilder(ExceptionPredicates).OrResult(resultPredicate); - - /// - /// Specifies a result value which the policy will handle. - /// - /// The type of return values this policy will handle. - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(TResult result) - => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); - - #endregion + private static Exception HandleInnerNested(Func predicate, Exception current) + { + if (current == null) return null; + if (predicate(current)) return current; + return HandleInnerNested(predicate, current.InnerException); } - public partial class PolicyBuilder + + #endregion + + #region Add result predicates to exception-filtering policy + + /// + /// Specifies the type of result that this policy can handle with additional filters on the result. + /// + /// The type of return values this policy will handle. + /// The predicate to filter the results this policy will handle. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(Func resultPredicate) + => new PolicyBuilder(ExceptionPredicates).OrResult(resultPredicate); + + /// + /// Specifies a result value which the policy will handle. + /// + /// The type of return values this policy will handle. + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(TResult result) + => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); + + #endregion +} +public partial class PolicyBuilder +{ + + #region Add result predicates to result-filtering policy + + /// + /// Specifies the type of result that this policy can handle with additional filters on the result. + /// + /// The predicate to filter the results this policy will handle. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(Func resultPredicate) { + ResultPredicate predicate = result => resultPredicate(result); + ResultPredicates.Add(predicate); + return this; + } - #region Add result predicates to result-filtering policy + /// + /// Specifies a result value which the policy will handle. + /// + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(TResult result) + => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); + + #endregion + + #region Add exception predicates to result-filtering policy + + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or() where TException : Exception + { + ExceptionPredicates.Add(exception => exception is TException ? exception : null); + return this; + } - /// - /// Specifies the type of result that this policy can handle with additional filters on the result. - /// - /// The predicate to filter the results this policy will handle. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(Func resultPredicate) - { - ResultPredicate predicate = result => resultPredicate(result); - ResultPredicates.Add(predicate); - return this; - } - - /// - /// Specifies a result value which the policy will handle. - /// - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(TResult result) - => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); - - #endregion - - #region Add exception predicates to result-filtering policy - - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or() where TException : Exception - { - ExceptionPredicates.Add(exception => exception is TException ? exception : null); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception - { - ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner() where TException : Exception - { - ExceptionPredicates.Add((PolicyBuilder.HandleInner(ex => ex is TException))); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception - { - ExceptionPredicates.Add(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); - return this; - } + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + return this; + } - #endregion + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner() where TException : Exception + { + ExceptionPredicates.Add((PolicyBuilder.HandleInner(ex => ex is TException))); + return this; } + + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); + return this; + } + + #endregion } diff --git a/src/Polly/PolicyBuilder.cs b/src/Polly/PolicyBuilder.cs index a00802069d5..e4628f043ed 100644 --- a/src/Polly/PolicyBuilder.cs +++ b/src/Polly/PolicyBuilder.cs @@ -1,148 +1,147 @@ using System; using System.ComponentModel; -namespace Polly +namespace Polly; + +/// +/// Builder class that holds the list of current exception predicates. +/// +public sealed partial class PolicyBuilder { + internal PolicyBuilder(ExceptionPredicate exceptionPredicate) + { + ExceptionPredicates = new ExceptionPredicates(); + ExceptionPredicates.Add(exceptionPredicate); + } + + /// + /// Predicates specifying exceptions that the policy is being configured to handle. + /// + internal ExceptionPredicates ExceptionPredicates { get; } + + #region Hide object members + /// - /// Builder class that holds the list of current exception predicates. + /// Returns a that represents this instance. /// - public sealed partial class PolicyBuilder + /// + /// A that represents this instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() { - internal PolicyBuilder(ExceptionPredicate exceptionPredicate) - { - ExceptionPredicates = new ExceptionPredicates(); - ExceptionPredicates.Add(exceptionPredicate); - } - - /// - /// Predicates specifying exceptions that the policy is being configured to handle. - /// - internal ExceptionPredicates ExceptionPredicates { get; } - - #region Hide object members - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string ToString() - { - return base.ToString(); - } - - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) - { - return base.Equals(obj); - } - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() - { - return base.GetHashCode(); - } - - /// - /// Gets the of the current instance. - /// - /// - /// The instance that represents the exact runtime type of the current instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public new Type GetType() - { - return base.GetType(); - } - - #endregion + return base.ToString(); } /// - /// Builder class that holds the list of current execution predicates filtering TResult result values. + /// Determines whether the specified is equal to this instance. /// - public sealed partial class PolicyBuilder + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + /// Gets the of the current instance. + /// + /// + /// The instance that represents the exact runtime type of the current instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + + #endregion +} + +/// +/// Builder class that holds the list of current execution predicates filtering TResult result values. +/// +public sealed partial class PolicyBuilder +{ + private PolicyBuilder() { - private PolicyBuilder() - { - ExceptionPredicates = new ExceptionPredicates(); - ResultPredicates = new ResultPredicates(); - } - - internal PolicyBuilder(Func resultPredicate) : this() - => OrResult(resultPredicate); - - internal PolicyBuilder(ExceptionPredicate predicate) : this() - => ExceptionPredicates.Add(predicate); - - internal PolicyBuilder(ExceptionPredicates exceptionPredicates) - : this() - => ExceptionPredicates = exceptionPredicates; - - /// - /// Predicates specifying exceptions that the policy is being configured to handle. - /// - internal ExceptionPredicates ExceptionPredicates { get; } - - /// - /// Predicates specifying results that the policy is being configured to handle. - /// - internal ResultPredicates ResultPredicates { get; } - - #region Hide object members - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string ToString() => base.ToString(); - - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => base.Equals(obj); - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); - - /// - /// Gets the of the current instance. - /// - /// - /// The instance that represents the exact runtime type of the current instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public new Type GetType() => base.GetType(); - - #endregion + ExceptionPredicates = new ExceptionPredicates(); + ResultPredicates = new ResultPredicates(); } -} \ No newline at end of file + + internal PolicyBuilder(Func resultPredicate) : this() + => OrResult(resultPredicate); + + internal PolicyBuilder(ExceptionPredicate predicate) : this() + => ExceptionPredicates.Add(predicate); + + internal PolicyBuilder(ExceptionPredicates exceptionPredicates) + : this() + => ExceptionPredicates = exceptionPredicates; + + /// + /// Predicates specifying exceptions that the policy is being configured to handle. + /// + internal ExceptionPredicates ExceptionPredicates { get; } + + /// + /// Predicates specifying results that the policy is being configured to handle. + /// + internal ResultPredicates ResultPredicates { get; } + + #region Hide object members + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() => base.ToString(); + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => base.Equals(obj); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + /// Gets the of the current instance. + /// + /// + /// The instance that represents the exact runtime type of the current instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() => base.GetType(); + + #endregion +} diff --git a/src/Polly/PolicyResult.cs b/src/Polly/PolicyResult.cs index 81f5c2c08bb..5452f1f6d80 100644 --- a/src/Polly/PolicyResult.cs +++ b/src/Polly/PolicyResult.cs @@ -1,209 +1,208 @@ using System; -namespace Polly +namespace Polly; + +/// +/// The captured result of executing a policy +/// +public class PolicyResult { - /// - /// The captured result of executing a policy - /// - public class PolicyResult + internal PolicyResult(OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) { - internal PolicyResult(OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) - { - Outcome = outcome; - FinalException = finalException; - ExceptionType = exceptionType; - Context = context; - } - - /// - /// The outcome of executing the policy - /// - public OutcomeType Outcome { get; } - - /// - /// The final exception captured. Will be null if policy executed successfully - /// - public Exception FinalException { get; } - - /// - /// The exception type of the final exception captured. Will be null if policy executed successfully - /// - public ExceptionType? ExceptionType { get; } - - /// - /// The context for this execution. - /// - public Context Context { get; } - - /// - /// Builds a representing a successful execution through the policy. - /// - /// The policy execution context - /// - /// A representing a successful execution through the policy. - /// - public static PolicyResult Successful(Context context) - => new PolicyResult(OutcomeType.Successful, null, null, context); - - /// - /// Builds a representing a failed execution through the policy. /> - /// - /// The exception - /// The exception type - /// The policy execution context - /// - /// A representing a failed execution through the policy. - /// - public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) - => new PolicyResult(OutcomeType.Failure, exception, exceptionType, context); + Outcome = outcome; + FinalException = finalException; + ExceptionType = exceptionType; + Context = context; } /// - /// The captured result of executing a policy + /// The outcome of executing the policy /// - public class PolicyResult - { - internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) - : this(result, outcome, finalException, exceptionType, default, null, context) - { - - } - - internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, TResult finalHandledResult, FaultType? faultType, Context context) - { - Result = result; - Outcome = outcome; - FinalException = finalException; - ExceptionType = exceptionType; - FinalHandledResult = finalHandledResult; - FaultType = faultType; - Context = context; - } - - /// - /// The outcome of executing the policy - /// - public OutcomeType Outcome { get; } - - /// - /// The final exception captured. Will be null if policy executed without exception. - /// - public Exception FinalException { get; } - - /// - /// The exception type of the final exception captured. Will be null if policy executed successfully - /// - public ExceptionType? ExceptionType { get; } - - /// - /// The result of executing the policy. Will be default(TResult) if the policy failed - /// - public TResult Result { get; } - - /// - /// The final handled result captured. Will be default(TResult) if the policy executed successfully, or terminated with an exception. - /// - public TResult FinalHandledResult { get; } - - /// - /// The fault type of the final fault captured. Will be null if policy executed successfully - /// - public FaultType? FaultType { get; } - - /// - /// The context for this execution. - /// - public Context Context { get; } - - /// - /// Builds a representing a successful execution through the policy. - /// - /// The result returned by execution through the policy - /// The policy execution context - /// - /// A representing a successful execution through the policy. - /// - public static PolicyResult Successful(TResult result, Context context) - => new PolicyResult(result, OutcomeType.Successful, null, null, context); - - /// - /// Builds a representing a failed execution through the policy. - /// - /// The exception - /// The exception type - /// The policy execution context - /// - /// A representing a failed execution through the policy. - /// - public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) - => new PolicyResult(default, OutcomeType.Failure, exception, exceptionType, default, - exceptionType == Polly.ExceptionType.HandledByThisPolicy - ? Polly.FaultType.ExceptionHandledByThisPolicy - : Polly.FaultType.UnhandledException, - context); - - /// - /// Builds a representing a failed execution through the policy. - /// - /// The result returned by execution through the policy, which was treated as a handled failure - /// The policy execution context - /// - /// A representing a failed execution through the policy. - /// - public static PolicyResult Failure(TResult handledResult, Context context) - => new PolicyResult(default, OutcomeType.Failure, null, null, handledResult, Polly.FaultType.ResultHandledByThisPolicy, context); - } + public OutcomeType Outcome { get; } /// - /// Represents the outcome of executing a policy + /// The final exception captured. Will be null if policy executed successfully /// - public enum OutcomeType - { - /// - /// Indicates that the policy ultimately executed successfully - /// - Successful, + public Exception FinalException { get; } - /// - /// Indicates that the policy ultimately failed - /// - Failure - }; + /// + /// The exception type of the final exception captured. Will be null if policy executed successfully + /// + public ExceptionType? ExceptionType { get; } /// - /// Represents the type of exception resulting from a failed policy + /// The context for this execution. /// - public enum ExceptionType - { - /// - /// An exception type that has been defined to be handled by this policy - /// - HandledByThisPolicy, - - /// - /// An exception type that has been not been defined to be handled by this policy - /// - Unhandled - } + public Context Context { get; } /// - /// Represents the type of outcome from a failed policy + /// Builds a representing a successful execution through the policy. /// - public enum FaultType + /// The policy execution context + /// + /// A representing a successful execution through the policy. + /// + public static PolicyResult Successful(Context context) + => new PolicyResult(OutcomeType.Successful, null, null, context); + + /// + /// Builds a representing a failed execution through the policy. /> + /// + /// The exception + /// The exception type + /// The policy execution context + /// + /// A representing a failed execution through the policy. + /// + public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) + => new PolicyResult(OutcomeType.Failure, exception, exceptionType, context); +} + +/// +/// The captured result of executing a policy +/// +public class PolicyResult +{ + internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) + : this(result, outcome, finalException, exceptionType, default, null, context) + { + + } + + internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, TResult finalHandledResult, FaultType? faultType, Context context) { - /// - /// An exception type that has been defined to be handled by this policy - /// - ExceptionHandledByThisPolicy, - - /// - /// An exception type that has been not been defined to be handled by this policy - /// - UnhandledException, - - /// - /// A result value that has been defined to be handled by this policy - /// - ResultHandledByThisPolicy + Result = result; + Outcome = outcome; + FinalException = finalException; + ExceptionType = exceptionType; + FinalHandledResult = finalHandledResult; + FaultType = faultType; + Context = context; } -} \ No newline at end of file + + /// + /// The outcome of executing the policy + /// + public OutcomeType Outcome { get; } + + /// + /// The final exception captured. Will be null if policy executed without exception. + /// + public Exception FinalException { get; } + + /// + /// The exception type of the final exception captured. Will be null if policy executed successfully + /// + public ExceptionType? ExceptionType { get; } + + /// + /// The result of executing the policy. Will be default(TResult) if the policy failed + /// + public TResult Result { get; } + + /// + /// The final handled result captured. Will be default(TResult) if the policy executed successfully, or terminated with an exception. + /// + public TResult FinalHandledResult { get; } + + /// + /// The fault type of the final fault captured. Will be null if policy executed successfully + /// + public FaultType? FaultType { get; } + + /// + /// The context for this execution. + /// + public Context Context { get; } + + /// + /// Builds a representing a successful execution through the policy. + /// + /// The result returned by execution through the policy + /// The policy execution context + /// + /// A representing a successful execution through the policy. + /// + public static PolicyResult Successful(TResult result, Context context) + => new PolicyResult(result, OutcomeType.Successful, null, null, context); + + /// + /// Builds a representing a failed execution through the policy. + /// + /// The exception + /// The exception type + /// The policy execution context + /// + /// A representing a failed execution through the policy. + /// + public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) + => new PolicyResult(default, OutcomeType.Failure, exception, exceptionType, default, + exceptionType == Polly.ExceptionType.HandledByThisPolicy + ? Polly.FaultType.ExceptionHandledByThisPolicy + : Polly.FaultType.UnhandledException, + context); + + /// + /// Builds a representing a failed execution through the policy. + /// + /// The result returned by execution through the policy, which was treated as a handled failure + /// The policy execution context + /// + /// A representing a failed execution through the policy. + /// + public static PolicyResult Failure(TResult handledResult, Context context) + => new PolicyResult(default, OutcomeType.Failure, null, null, handledResult, Polly.FaultType.ResultHandledByThisPolicy, context); +} + +/// +/// Represents the outcome of executing a policy +/// +public enum OutcomeType +{ + /// + /// Indicates that the policy ultimately executed successfully + /// + Successful, + + /// + /// Indicates that the policy ultimately failed + /// + Failure +}; + +/// +/// Represents the type of exception resulting from a failed policy +/// +public enum ExceptionType +{ + /// + /// An exception type that has been defined to be handled by this policy + /// + HandledByThisPolicy, + + /// + /// An exception type that has been not been defined to be handled by this policy + /// + Unhandled +} + +/// +/// Represents the type of outcome from a failed policy +/// +public enum FaultType +{ + /// + /// An exception type that has been defined to be handled by this policy + /// + ExceptionHandledByThisPolicy, + + /// + /// An exception type that has been not been defined to be handled by this policy + /// + UnhandledException, + + /// + /// A result value that has been defined to be handled by this policy + /// + ResultHandledByThisPolicy +} diff --git a/src/Polly/RateLimit/AsyncRateLimitEngine.cs b/src/Polly/RateLimit/AsyncRateLimitEngine.cs index 16f40caec77..fabb362c3ee 100644 --- a/src/Polly/RateLimit/AsyncRateLimitEngine.cs +++ b/src/Polly/RateLimit/AsyncRateLimitEngine.cs @@ -2,32 +2,31 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.RateLimit +namespace Polly.RateLimit; + +internal static class AsyncRateLimitEngine { - internal static class AsyncRateLimitEngine + internal static async Task ImplementationAsync( + IRateLimiter rateLimiter, + Func retryAfterFactory, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext + ) { - internal static async Task ImplementationAsync( - IRateLimiter rateLimiter, - Func retryAfterFactory, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext - ) - { - (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution(); + (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution(); - if (permit) - { - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } - - if (retryAfterFactory != null) - { - return retryAfterFactory(retryAfter, context); - } + if (permit) + { + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } - throw new RateLimitRejectedException(retryAfter); + if (retryAfterFactory != null) + { + return retryAfterFactory(retryAfter, context); } + + throw new RateLimitRejectedException(retryAfter); } } diff --git a/src/Polly/RateLimit/AsyncRateLimitPolicy.cs b/src/Polly/RateLimit/AsyncRateLimitPolicy.cs index 261a9ea3dee..be7590aac2b 100644 --- a/src/Polly/RateLimit/AsyncRateLimitPolicy.cs +++ b/src/Polly/RateLimit/AsyncRateLimitPolicy.cs @@ -3,47 +3,46 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.RateLimit -{ - /// - /// A rate-limit policy that can be applied to asynchronous delegates. - /// - public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy - { - private readonly IRateLimiter _rateLimiter; +namespace Polly.RateLimit; - internal AsyncRateLimitPolicy(IRateLimiter rateLimiter) - { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - } +/// +/// A rate-limit policy that can be applied to asynchronous delegates. +/// +public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy +{ + private readonly IRateLimiter _rateLimiter; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, null, action, context, cancellationToken, continueOnCapturedContext); + internal AsyncRateLimitPolicy(IRateLimiter rateLimiter) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); } - /// - /// A rate-limit policy that can be applied to asynchronous delegates returning a value of type . - /// - public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy - { - private readonly IRateLimiter _rateLimiter; - private readonly Func _retryAfterFactory; + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, null, action, context, cancellationToken, continueOnCapturedContext); +} - internal AsyncRateLimitPolicy( - IRateLimiter rateLimiter, - Func retryAfterFactory) - { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - _retryAfterFactory = retryAfterFactory; - } +/// +/// A rate-limit policy that can be applied to asynchronous delegates returning a value of type . +/// +public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy +{ + private readonly IRateLimiter _rateLimiter; + private readonly Func _retryAfterFactory; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, _retryAfterFactory, action, context, cancellationToken, continueOnCapturedContext); + internal AsyncRateLimitPolicy( + IRateLimiter rateLimiter, + Func retryAfterFactory) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); + _retryAfterFactory = retryAfterFactory; } -} \ No newline at end of file + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, _retryAfterFactory, action, context, cancellationToken, continueOnCapturedContext); +} diff --git a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs index 2e76dce61e9..db0d6813898 100644 --- a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs @@ -1,50 +1,49 @@ using System; using Polly.RateLimit; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan) { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan) - { - return RateLimitAsync(numberOfExecutions, perTimeSpan, 1); - } + return RateLimitAsync(numberOfExecutions, perTimeSpan, 1); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new AsyncRateLimitPolicy(rateLimiter); - } + return new AsyncRateLimitPolicy(rateLimiter); } } diff --git a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs index 1a07d805e29..4844f885013 100644 --- a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs @@ -1,90 +1,89 @@ using System; using Polly.RateLimit; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan) { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan) - { - return RateLimitAsync(numberOfExecutions, perTimeSpan, null); - } + return RateLimitAsync(numberOfExecutions, perTimeSpan, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - Func retryAfterFactory) - { - return RateLimitAsync(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + Func retryAfterFactory) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - return RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, null); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given, - /// with a maximum burst size of - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst, - Func retryAfterFactory) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given, + /// with a maximum burst size of + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst, + Func retryAfterFactory) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new AsyncRateLimitPolicy(rateLimiter, retryAfterFactory); - } + return new AsyncRateLimitPolicy(rateLimiter, retryAfterFactory); } } diff --git a/src/Polly/RateLimit/IRateLimitPolicy.cs b/src/Polly/RateLimit/IRateLimitPolicy.cs index 88c967e0c3c..89cf8404204 100644 --- a/src/Polly/RateLimit/IRateLimitPolicy.cs +++ b/src/Polly/RateLimit/IRateLimitPolicy.cs @@ -1,16 +1,15 @@ -namespace Polly.RateLimit +namespace Polly.RateLimit; + +/// +/// Defines properties and methods common to all RateLimit policies. +/// +public interface IRateLimitPolicy : IsPolicy { - /// - /// Defines properties and methods common to all RateLimit policies. - /// - public interface IRateLimitPolicy : IsPolicy - { - } +} - /// - /// Defines properties and methods common to all RateLimit policies generic-typed for executions returning results of type . - /// - public interface IRateLimitPolicy : IRateLimitPolicy - { - } +/// +/// Defines properties and methods common to all RateLimit policies generic-typed for executions returning results of type . +/// +public interface IRateLimitPolicy : IRateLimitPolicy +{ } diff --git a/src/Polly/RateLimit/IRateLimiter.cs b/src/Polly/RateLimit/IRateLimiter.cs index 79e724f3acb..74919b97ae9 100644 --- a/src/Polly/RateLimit/IRateLimiter.cs +++ b/src/Polly/RateLimit/IRateLimiter.cs @@ -1,16 +1,15 @@ using System; -namespace Polly.RateLimit +namespace Polly.RateLimit; + +/// +/// Defines methods to be provided by a rate-limiter used in a Polly +/// +internal interface IRateLimiter { /// - /// Defines methods to be provided by a rate-limiter used in a Polly + /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. + /// Calling this method consumes an execution permit if one is available: a caller receiving a return value true should make an execution. /// - internal interface IRateLimiter - { - /// - /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. - /// Calling this method consumes an execution permit if one is available: a caller receiving a return value true should make an execution. - /// - (bool permitExecution, TimeSpan retryAfter) PermitExecution(); - } + (bool permitExecution, TimeSpan retryAfter) PermitExecution(); } diff --git a/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs b/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs index b4b3dd54841..7120152169f 100644 --- a/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs +++ b/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs @@ -2,114 +2,113 @@ using System.Threading; using Polly.Utilities; -namespace Polly.RateLimit +namespace Polly.RateLimit; + +/// +/// A lock-free token-bucket rate-limiter for a Polly . +/// +internal sealed class LockFreeTokenBucketRateLimiter : IRateLimiter { - /// - /// A lock-free token-bucket rate-limiter for a Polly . - /// - internal sealed class LockFreeTokenBucketRateLimiter : IRateLimiter - { - private readonly long addTokenTickInterval; - private readonly long bucketCapacity; + private readonly long addTokenTickInterval; + private readonly long bucketCapacity; - private long currentTokens; + private long currentTokens; - private long addNextTokenAtTicks; + private long addNextTokenAtTicks; #if !NETSTANDARD2_0 - private SpinWait spinner = new(); + private SpinWait spinner = new(); #endif - /// - /// Creates an instance of - /// - /// How often one execution is permitted. - /// The capacity of the token bucket. - /// This equates to the maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// - public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity) - { - if (onePer <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(onePer), onePer, $"The {nameof(LockFreeTokenBucketRateLimiter)} must specify a positive TimeSpan for how often an execution is permitted."); - if (bucketCapacity <= 0) throw new ArgumentOutOfRangeException(nameof(bucketCapacity), bucketCapacity, $"{nameof(bucketCapacity)} must be greater than or equal to 1."); + /// + /// Creates an instance of + /// + /// How often one execution is permitted. + /// The capacity of the token bucket. + /// This equates to the maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// + public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity) + { + if (onePer <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(onePer), onePer, $"The {nameof(LockFreeTokenBucketRateLimiter)} must specify a positive TimeSpan for how often an execution is permitted."); + if (bucketCapacity <= 0) throw new ArgumentOutOfRangeException(nameof(bucketCapacity), bucketCapacity, $"{nameof(bucketCapacity)} must be greater than or equal to 1."); - addTokenTickInterval = onePer.Ticks; - this.bucketCapacity = bucketCapacity; + addTokenTickInterval = onePer.Ticks; + this.bucketCapacity = bucketCapacity; - currentTokens = bucketCapacity; - addNextTokenAtTicks = SystemClock.DateTimeOffsetUtcNow().Ticks + addTokenTickInterval; - } + currentTokens = bucketCapacity; + addNextTokenAtTicks = SystemClock.DateTimeOffsetUtcNow().Ticks + addTokenTickInterval; + } - /// - /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. - /// - public (bool permitExecution, TimeSpan retryAfter) PermitExecution() + /// + /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. + /// + public (bool permitExecution, TimeSpan retryAfter) PermitExecution() + { + while (true) { - while (true) + // Try to get a token. + long tokensAfterGrabOne = Interlocked.Decrement(ref currentTokens); + + if (tokensAfterGrabOne >= 0) { - // Try to get a token. - long tokensAfterGrabOne = Interlocked.Decrement(ref currentTokens); - - if (tokensAfterGrabOne >= 0) - { - // We got a token: permit execution! - return (true, TimeSpan.Zero); - } - - // No tokens! We're rate-limited - unless we can refill the bucket. - long now = SystemClock.DateTimeOffsetUtcNow().Ticks; - long currentAddNextTokenAtTicks = Interlocked.Read(ref addNextTokenAtTicks); - long ticksTillAddNextToken = currentAddNextTokenAtTicks - now; - - if (ticksTillAddNextToken > 0) - { - // Not time to add tokens yet: we're rate-limited! - return (false, TimeSpan.FromTicks(ticksTillAddNextToken)); - } - - // Time to add tokens to the bucket! - - // We definitely need to add one token. In fact, if we haven't hit this bit of code for a while, we might be due to add a bunch of tokens. - long tokensMissedAdding = - // Passing addNextTokenAtTicks merits one token - 1 + - // And any whole token tick intervals further each merit another. - (-ticksTillAddNextToken / addTokenTickInterval); - - // We mustn't exceed bucket capacity though. - long tokensToAdd = Math.Min(bucketCapacity, tokensMissedAdding); - - // Work out when tokens would next be due to be added, if we add these tokens. - long newAddNextTokenAtTicks = currentAddNextTokenAtTicks + (tokensToAdd * addTokenTickInterval); - // But if we were way overdue refilling the bucket (there was inactivity for a while), that value would be out-of-date: the next time we add tokens must be at least addTokenTickInterval from now. - newAddNextTokenAtTicks = Math.Max(newAddNextTokenAtTicks, now + addTokenTickInterval); - - // Now see if we win the race to add these tokens. Other threads might be racing through this code at the same time: only one thread must add the tokens! - if (Interlocked.CompareExchange(ref addNextTokenAtTicks, newAddNextTokenAtTicks, currentAddNextTokenAtTicks) == currentAddNextTokenAtTicks) - { - // We won the race to add the tokens! - - // Theoretically we want to add tokensToAdd tokens. But in fact we don't do that. - // We want to claim one of those tokens for ourselves - there's no way we're going to add it but let another thread snatch it from under our nose. - // (Doing that could leave this thread looping round adding tokens for ever which other threads just snatch - would lead to odd observed behaviour.) - - // So in fact we add (tokensToAdd - 1) tokens (ie we consume one), and return, permitting this execution. - - // The advantage of only adding tokens when the bucket is empty is that we can now hard set the new amount of tokens (Interlocked.Exchange) without caring if other threads have simultaneously been taking or adding tokens. - // (If we added a token per addTokenTickInterval to a non-empty bucket, the reasoning about not overflowing the bucket seems harder.) - Interlocked.Exchange(ref currentTokens, tokensToAdd - 1); - return (true, TimeSpan.Zero); - } - else - { - // We didn't win the race to add the tokens. BUT because it _was_ time to add tokens, another thread must have won that race and have added/be adding tokens, so there _may_ be more tokens, so loop and try again. - - // We want any thread refilling the bucket to have a chance to do so before we try to grab the next token. + // We got a token: permit execution! + return (true, TimeSpan.Zero); + } + + // No tokens! We're rate-limited - unless we can refill the bucket. + long now = SystemClock.DateTimeOffsetUtcNow().Ticks; + long currentAddNextTokenAtTicks = Interlocked.Read(ref addNextTokenAtTicks); + long ticksTillAddNextToken = currentAddNextTokenAtTicks - now; + + if (ticksTillAddNextToken > 0) + { + // Not time to add tokens yet: we're rate-limited! + return (false, TimeSpan.FromTicks(ticksTillAddNextToken)); + } + + // Time to add tokens to the bucket! + + // We definitely need to add one token. In fact, if we haven't hit this bit of code for a while, we might be due to add a bunch of tokens. + long tokensMissedAdding = + // Passing addNextTokenAtTicks merits one token + 1 + + // And any whole token tick intervals further each merit another. + (-ticksTillAddNextToken / addTokenTickInterval); + + // We mustn't exceed bucket capacity though. + long tokensToAdd = Math.Min(bucketCapacity, tokensMissedAdding); + + // Work out when tokens would next be due to be added, if we add these tokens. + long newAddNextTokenAtTicks = currentAddNextTokenAtTicks + (tokensToAdd * addTokenTickInterval); + // But if we were way overdue refilling the bucket (there was inactivity for a while), that value would be out-of-date: the next time we add tokens must be at least addTokenTickInterval from now. + newAddNextTokenAtTicks = Math.Max(newAddNextTokenAtTicks, now + addTokenTickInterval); + + // Now see if we win the race to add these tokens. Other threads might be racing through this code at the same time: only one thread must add the tokens! + if (Interlocked.CompareExchange(ref addNextTokenAtTicks, newAddNextTokenAtTicks, currentAddNextTokenAtTicks) == currentAddNextTokenAtTicks) + { + // We won the race to add the tokens! + + // Theoretically we want to add tokensToAdd tokens. But in fact we don't do that. + // We want to claim one of those tokens for ourselves - there's no way we're going to add it but let another thread snatch it from under our nose. + // (Doing that could leave this thread looping round adding tokens for ever which other threads just snatch - would lead to odd observed behaviour.) + + // So in fact we add (tokensToAdd - 1) tokens (ie we consume one), and return, permitting this execution. + + // The advantage of only adding tokens when the bucket is empty is that we can now hard set the new amount of tokens (Interlocked.Exchange) without caring if other threads have simultaneously been taking or adding tokens. + // (If we added a token per addTokenTickInterval to a non-empty bucket, the reasoning about not overflowing the bucket seems harder.) + Interlocked.Exchange(ref currentTokens, tokensToAdd - 1); + return (true, TimeSpan.Zero); + } + else + { + // We didn't win the race to add the tokens. BUT because it _was_ time to add tokens, another thread must have won that race and have added/be adding tokens, so there _may_ be more tokens, so loop and try again. + + // We want any thread refilling the bucket to have a chance to do so before we try to grab the next token. #if NETSTANDARD2_0 - Thread.Sleep(0); + Thread.Sleep(0); #else - spinner.SpinOnce(); + spinner.SpinOnce(); #endif - } } } } diff --git a/src/Polly/RateLimit/RateLimitEngine.cs b/src/Polly/RateLimit/RateLimitEngine.cs index a96cbb2835d..80dd425362d 100644 --- a/src/Polly/RateLimit/RateLimitEngine.cs +++ b/src/Polly/RateLimit/RateLimitEngine.cs @@ -1,31 +1,30 @@ using System; using System.Threading; -namespace Polly.RateLimit +namespace Polly.RateLimit; + +internal static class RateLimitEngine { - internal static class RateLimitEngine + internal static TResult Implementation( + IRateLimiter rateLimiter, + Func retryAfterFactory, + Func action, + Context context, + CancellationToken cancellationToken + ) { - internal static TResult Implementation( - IRateLimiter rateLimiter, - Func retryAfterFactory, - Func action, - Context context, - CancellationToken cancellationToken - ) - { - (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution(); + (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution(); - if (permit) - { - return action(context, cancellationToken); - } - - if (retryAfterFactory != null) - { - return retryAfterFactory(retryAfter, context); - } + if (permit) + { + return action(context, cancellationToken); + } - throw new RateLimitRejectedException(retryAfter); + if (retryAfterFactory != null) + { + return retryAfterFactory(retryAfter, context); } + + throw new RateLimitRejectedException(retryAfter); } -} \ No newline at end of file +} diff --git a/src/Polly/RateLimit/RateLimitPolicy.cs b/src/Polly/RateLimit/RateLimitPolicy.cs index aa0106f94f6..f1a09defc62 100644 --- a/src/Polly/RateLimit/RateLimitPolicy.cs +++ b/src/Polly/RateLimit/RateLimitPolicy.cs @@ -2,45 +2,44 @@ using System.Diagnostics; using System.Threading; -namespace Polly.RateLimit -{ - /// - /// A rate-limit policy that can be applied to synchronous delegates. - /// - public class RateLimitPolicy : Policy, IRateLimitPolicy - { - private readonly IRateLimiter _rateLimiter; +namespace Polly.RateLimit; - internal RateLimitPolicy(IRateLimiter rateLimiter) - { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - } +/// +/// A rate-limit policy that can be applied to synchronous delegates. +/// +public class RateLimitPolicy : Policy, IRateLimitPolicy +{ + private readonly IRateLimiter _rateLimiter; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RateLimitEngine.Implementation(_rateLimiter, null, action, context, cancellationToken); + internal RateLimitPolicy(IRateLimiter rateLimiter) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); } - /// - /// A rate-limit policy that can be applied to synchronous delegates returning a value of type . - /// - public class RateLimitPolicy : Policy, IRateLimitPolicy - { - private readonly IRateLimiter _rateLimiter; - private readonly Func _retryAfterFactory; + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RateLimitEngine.Implementation(_rateLimiter, null, action, context, cancellationToken); +} - internal RateLimitPolicy( - IRateLimiter rateLimiter, - Func retryAfterFactory) - { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - _retryAfterFactory = retryAfterFactory; - } +/// +/// A rate-limit policy that can be applied to synchronous delegates returning a value of type . +/// +public class RateLimitPolicy : Policy, IRateLimitPolicy +{ + private readonly IRateLimiter _rateLimiter; + private readonly Func _retryAfterFactory; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RateLimitEngine.Implementation(_rateLimiter, _retryAfterFactory, action, context, cancellationToken); + internal RateLimitPolicy( + IRateLimiter rateLimiter, + Func retryAfterFactory) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); + _retryAfterFactory = retryAfterFactory; } -} \ No newline at end of file + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RateLimitEngine.Implementation(_rateLimiter, _retryAfterFactory, action, context, cancellationToken); +} diff --git a/src/Polly/RateLimit/RateLimitRejectedException.cs b/src/Polly/RateLimit/RateLimitRejectedException.cs index d30b1557ab9..f2043e569e1 100644 --- a/src/Polly/RateLimit/RateLimitRejectedException.cs +++ b/src/Polly/RateLimit/RateLimitRejectedException.cs @@ -3,77 +3,76 @@ using System.Runtime.Serialization; #endif -namespace Polly.RateLimit +namespace Polly.RateLimit; + +/// +/// Exception thrown when a delegate executed through a is rate-limited. +/// +#if NETSTANDARD2_0 +[Serializable] +#endif +public class RateLimitRejectedException : ExecutionRejectedException { /// - /// Exception thrown when a delegate executed through a is rate-limited. + /// The timespan after which the operation may be retried. /// -#if NETSTANDARD2_0 - [Serializable] -#endif - public class RateLimitRejectedException : ExecutionRejectedException - { - /// - /// The timespan after which the operation may be retried. - /// - public TimeSpan RetryAfter { get; private set; } + public TimeSpan RetryAfter { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The timespan after which the operation may be retried. - public RateLimitRejectedException(TimeSpan retryAfter) : this(retryAfter, DefaultMessage(retryAfter)) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The timespan after which the operation may be retried. + public RateLimitRejectedException(TimeSpan retryAfter) : this(retryAfter, DefaultMessage(retryAfter)) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The timespan after which the operation may be retried. - /// The inner exception. - public RateLimitRejectedException(TimeSpan retryAfter, Exception innerException) : base(DefaultMessage(retryAfter), innerException) - { - SetRetryAfter(retryAfter); - } + /// + /// Initializes a new instance of the class. + /// + /// The timespan after which the operation may be retried. + /// The inner exception. + public RateLimitRejectedException(TimeSpan retryAfter, Exception innerException) : base(DefaultMessage(retryAfter), innerException) + { + SetRetryAfter(retryAfter); + } - /// - /// Initializes a new instance of the class. - /// - /// The timespan after which the operation may be retried. - /// The message. - public RateLimitRejectedException(TimeSpan retryAfter, string message) : base(message) - { - SetRetryAfter(retryAfter); - } + /// + /// Initializes a new instance of the class. + /// + /// The timespan after which the operation may be retried. + /// The message. + public RateLimitRejectedException(TimeSpan retryAfter, string message) : base(message) + { + SetRetryAfter(retryAfter); + } - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The timespan after which the operation may be retried. - /// The inner exception. - public RateLimitRejectedException(TimeSpan retryAfter, string message, Exception innerException) : base(message, innerException) - { - SetRetryAfter(retryAfter); - } + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The timespan after which the operation may be retried. + /// The inner exception. + public RateLimitRejectedException(TimeSpan retryAfter, string message, Exception innerException) : base(message, innerException) + { + SetRetryAfter(retryAfter); + } - private void SetRetryAfter(TimeSpan retryAfter) - { - if (retryAfter < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(retryAfter), retryAfter, $"The {nameof(retryAfter)} parameter must be a TimeSpan greater than or equal to TimeSpan.Zero."); - RetryAfter = retryAfter; - } + private void SetRetryAfter(TimeSpan retryAfter) + { + if (retryAfter < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(retryAfter), retryAfter, $"The {nameof(retryAfter)} parameter must be a TimeSpan greater than or equal to TimeSpan.Zero."); + RetryAfter = retryAfter; + } - private static string DefaultMessage(TimeSpan retryAfter) => $"The operation has been rate-limited and should be retried after {retryAfter}"; + private static string DefaultMessage(TimeSpan retryAfter) => $"The operation has been rate-limited and should be retried after {retryAfter}"; #if NETSTANDARD2_0 - /// - /// Initializes a new instance of the class. - /// - /// The information. - /// The context. - protected RateLimitRejectedException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// + /// The information. + /// The context. + protected RateLimitRejectedException(SerializationInfo info, StreamingContext context) : base(info, context) + { } +#endif } diff --git a/src/Polly/RateLimit/RateLimitSyntax.cs b/src/Polly/RateLimit/RateLimitSyntax.cs index fbdd993aa0a..5f509c9eb6d 100644 --- a/src/Polly/RateLimit/RateLimitSyntax.cs +++ b/src/Polly/RateLimit/RateLimitSyntax.cs @@ -1,50 +1,49 @@ using System; using Polly.RateLimit; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan) { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan) - { - return RateLimit(numberOfExecutions, perTimeSpan, 1); - } + return RateLimit(numberOfExecutions, perTimeSpan, 1); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new RateLimitPolicy(rateLimiter); - } + return new RateLimitPolicy(rateLimiter); } } diff --git a/src/Polly/RateLimit/RateLimitTResultSyntax.cs b/src/Polly/RateLimit/RateLimitTResultSyntax.cs index 17adaf53e7f..b6f09c6113b 100644 --- a/src/Polly/RateLimit/RateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/RateLimitTResultSyntax.cs @@ -1,90 +1,89 @@ using System; using Polly.RateLimit; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan) { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan) - { - return RateLimit(numberOfExecutions, perTimeSpan, null); - } + return RateLimit(numberOfExecutions, perTimeSpan, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - Func retryAfterFactory) - { - return RateLimit(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + Func retryAfterFactory) + { + return RateLimit(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - return RateLimit(numberOfExecutions, perTimeSpan, maxBurst, null); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + return RateLimit(numberOfExecutions, perTimeSpan, maxBurst, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given, - /// with a maximum burst size of - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst, - Func retryAfterFactory) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given, + /// with a maximum burst size of + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst, + Func retryAfterFactory) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new RateLimitPolicy(rateLimiter, retryAfterFactory); - } + return new RateLimitPolicy(rateLimiter, retryAfterFactory); } } diff --git a/src/Polly/RateLimit/RateLimiterFactory.cs b/src/Polly/RateLimit/RateLimiterFactory.cs index e4f01ee29bc..70bf40f2ec9 100644 --- a/src/Polly/RateLimit/RateLimiterFactory.cs +++ b/src/Polly/RateLimit/RateLimiterFactory.cs @@ -1,10 +1,9 @@ using System; -namespace Polly.RateLimit +namespace Polly.RateLimit; + +internal static class RateLimiterFactory { - internal static class RateLimiterFactory - { - public static IRateLimiter Create(TimeSpan onePer, int bucketCapacity) - => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); - } + public static IRateLimiter Create(TimeSpan onePer, int bucketCapacity) + => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); } diff --git a/src/Polly/Registry/IConcurrentPolicyRegistry.cs b/src/Polly/Registry/IConcurrentPolicyRegistry.cs index e703c093856..903eeee870d 100644 --- a/src/Polly/Registry/IConcurrentPolicyRegistry.cs +++ b/src/Polly/Registry/IConcurrentPolicyRegistry.cs @@ -1,90 +1,89 @@ using System; -namespace Polly.Registry +namespace Polly.Registry; + +/// +/// Represents a collection of policies keyed by which can be updated and consumed in a thread-safe manner. +/// +/// The type of keys in the policy registry. +public interface IConcurrentPolicyRegistry : IPolicyRegistry { /// - /// Represents a collection of policies keyed by which can be updated and consumed in a thread-safe manner. + /// Adds an element with the provided key and policy to the registry. /// - /// The type of keys in the policy registry. - public interface IConcurrentPolicyRegistry : IPolicyRegistry - { - /// - /// Adds an element with the provided key and policy to the registry. - /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// True if Policy was added. False otherwise. - bool TryAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// True if Policy was added. False otherwise. + bool TryAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The of the policy to remove. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if the policy is successfully removed. Otherwise false. - bool TryRemove(TKey key, out TPolicy policy) where TPolicy : IsPolicy; + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The of the policy to remove. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if the policy is successfully removed. Otherwise false. + bool TryRemove(TKey key, out TPolicy policy) where TPolicy : IsPolicy; - /// - /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. - /// - /// - /// The key whose value is compared with comparisonPolicy, and possibly replaced. - /// The policy that replaces the value for the specified , if the comparison results in equality. - /// The policy that is compared to the existing policy at the specified key. - /// - bool TryUpdate(TKey key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy; + /// + /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. + /// + /// + /// The key whose value is compared with comparisonPolicy, and possibly replaced. + /// The policy that replaces the value for the specified , if the comparison results in equality. + /// The policy that is compared to the existing policy at the specified key. + /// + bool TryUpdate(TKey key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy; - /// - /// Adds a policy with the provided key and policy to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// The function used to generate a policy for the key - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy for the key as returned by policyFactory - /// if the key was not in the registry. - TPolicy GetOrAdd(TKey key, Func policyFactory) where TPolicy : IsPolicy; + /// + /// Adds a policy with the provided key and policy to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// The function used to generate a policy for the key + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy for the key as returned by policyFactory + /// if the key was not in the registry. + TPolicy GetOrAdd(TKey key, Func policyFactory) where TPolicy : IsPolicy; - /// - /// Adds a key/policy pair to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// the value to be added, if the key does not already exist - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy if the key was not in the registry. - TPolicy GetOrAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; + /// + /// Adds a key/policy pair to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// the value to be added, if the key does not already exist + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy if the key was not in the registry. + TPolicy GetOrAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The function used to generate a policy for an absent key - /// The function used to generate a new policy for an existing key - /// based on the key's existing value - /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - TPolicy AddOrUpdate(TKey key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy; + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The function used to generate a policy for an absent key + /// The function used to generate a new policy for an existing key + /// based on the key's existing value + /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + TPolicy AddOrUpdate(TKey key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy; - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The policy to be added for an absent key - /// The function used to generate a new policy for an existing key based on - /// the key's existing value - /// The new policy for the key. This will be either be addPolicy (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - TPolicy AddOrUpdate(TKey key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy; - } + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The policy to be added for an absent key + /// The function used to generate a new policy for an existing key based on + /// the key's existing value + /// The new policy for the key. This will be either be addPolicy (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + TPolicy AddOrUpdate(TKey key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy; } diff --git a/src/Polly/Registry/IPolicyRegistry.cs b/src/Polly/Registry/IPolicyRegistry.cs index 59f309b140d..0cce3451a0b 100644 --- a/src/Polly/Registry/IPolicyRegistry.cs +++ b/src/Polly/Registry/IPolicyRegistry.cs @@ -1,45 +1,44 @@ using System; using System.Collections.Generic; -namespace Polly.Registry +namespace Polly.Registry; + +/// +/// Represents a collection of policies keyed by . +/// +/// The type of keys in the policy registry. +public interface IPolicyRegistry : IReadOnlyPolicyRegistry { /// - /// Represents a collection of policies keyed by . + /// Adds an element with the provided key and policy to the registry. /// - /// The type of keys in the policy registry. - public interface IPolicyRegistry : IReadOnlyPolicyRegistry - { - /// - /// Adds an element with the provided key and policy to the registry. - /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// is null. - /// A Policy with same already exists. - void Add(TKey key, TPolicy policy) where TPolicy : IsPolicy; + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// is null. + /// A Policy with same already exists. + void Add(TKey key, TPolicy policy) where TPolicy : IsPolicy; - /// - /// Gets or sets the with the specified key. - /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. - /// - /// The key of the value to get or set. - /// is null. - /// The given key was not present in the dictionary. - /// The value associated with the specified key. - new IsPolicy this[TKey key] { get; set; } + /// + /// Gets or sets the with the specified key. + /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. + /// + /// The key of the value to get or set. + /// is null. + /// The given key was not present in the dictionary. + /// The value associated with the specified key. + new IsPolicy this[TKey key] { get; set; } - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The key of the policy to remove. - /// True if the policy is successfully removed. Otherwise false. - /// is null. - bool Remove(TKey key); + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The key of the policy to remove. + /// True if the policy is successfully removed. Otherwise false. + /// is null. + bool Remove(TKey key); - /// - /// Removes all keys and policies from registry. - /// - void Clear(); - } + /// + /// Removes all keys and policies from registry. + /// + void Clear(); } diff --git a/src/Polly/Registry/IReadOnlyPolicyRegistry.cs b/src/Polly/Registry/IReadOnlyPolicyRegistry.cs index b457578be51..c938c637c60 100644 --- a/src/Polly/Registry/IReadOnlyPolicyRegistry.cs +++ b/src/Polly/Registry/IReadOnlyPolicyRegistry.cs @@ -1,56 +1,55 @@ using System; using System.Collections.Generic; -namespace Polly.Registry +namespace Polly.Registry; + +/// +/// Represents a read-only collection of policies keyed by . +/// +/// The type of keys in the policy registry. +public interface IReadOnlyPolicyRegistry : IEnumerable> { /// - /// Represents a read-only collection of policies keyed by . + /// Gets the with the specified key. + /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. /// - /// The type of keys in the policy registry. - public interface IReadOnlyPolicyRegistry : IEnumerable> - { - /// - /// Gets the with the specified key. - /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. - /// - /// The key of the value to get or set. - /// is null. - /// The given key was not present in the dictionary. - /// The value associated with the specified key. - IsPolicy this[TKey key] { get; } + /// The key of the value to get or set. + /// is null. + /// The given key was not present in the dictionary. + /// The value associated with the specified key. + IsPolicy this[TKey key] { get; } - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The type of Policy. - /// The policy stored in the registry under the given key. - /// is null. - TPolicy Get(TKey key) where TPolicy : IsPolicy; + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The type of Policy. + /// The policy stored in the registry under the given key. + /// is null. + TPolicy Get(TKey key) where TPolicy : IsPolicy; - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The key of the policy to get. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if Policy exists for the provided Key. False otherwise. - bool TryGet(TKey key, out TPolicy policy) where TPolicy : IsPolicy; + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The key of the policy to get. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if Policy exists for the provided Key. False otherwise. + bool TryGet(TKey key, out TPolicy policy) where TPolicy : IsPolicy; - /// - /// Total number of policies in the registry. - /// - int Count { get; } + /// + /// Total number of policies in the registry. + /// + int Count { get; } - /// - /// Determines whether the specified exists. - /// - /// The Key to locate in the registry - /// True if exists otherwise false - /// is null - bool ContainsKey(TKey key); - } -} \ No newline at end of file + /// + /// Determines whether the specified exists. + /// + /// The Key to locate in the registry + /// True if exists otherwise false + /// is null + bool ContainsKey(TKey key); +} diff --git a/src/Polly/Registry/PolicyRegistry.cs b/src/Polly/Registry/PolicyRegistry.cs index a724d4cb835..a2e04bad108 100644 --- a/src/Polly/Registry/PolicyRegistry.cs +++ b/src/Polly/Registry/PolicyRegistry.cs @@ -3,270 +3,269 @@ using System.Collections.Generic; using System.Collections.Concurrent; -namespace Polly.Registry +namespace Polly.Registry; + +/// +/// +/// Stores a registry of and policy pairs. +/// +/// Uses ConcurrentDictionary to store the collection. +public class PolicyRegistry : IConcurrentPolicyRegistry { - /// + private readonly IDictionary _registry = new ConcurrentDictionary(); + /// - /// Stores a registry of and policy pairs. + /// Creates a registry of policies with keys. /// - /// Uses ConcurrentDictionary to store the collection. - public class PolicyRegistry : IConcurrentPolicyRegistry + public PolicyRegistry() { - private readonly IDictionary _registry = new ConcurrentDictionary(); + // This empty public constructor must be retained while the adjacent internal constructor exists for testing. + // The integration with HttpClientFactory, method services.AddPolicyRegistry(), depends on this empty public constructor. + // Do not collapse the two constructors into a constructor with optional parameter registry == null. + // That breaks the requirement for a noargs public constructor, against which nuget-published .NET Core dlls have been compiled. + } - /// - /// Creates a registry of policies with keys. - /// - public PolicyRegistry() - { - // This empty public constructor must be retained while the adjacent internal constructor exists for testing. - // The integration with HttpClientFactory, method services.AddPolicyRegistry(), depends on this empty public constructor. - // Do not collapse the two constructors into a constructor with optional parameter registry == null. - // That breaks the requirement for a noargs public constructor, against which nuget-published .NET Core dlls have been compiled. - } + /// + /// Creates a registry of policies with keys. + /// This internal constructor exists solely to facilitate testing of the GetEnumerator() methods, which allow us to support collection initialisation syntax. + /// + /// a dictionary containing keys and policies used for testing. + internal PolicyRegistry(IDictionary registry) + { + _registry = registry ?? throw new NullReferenceException(nameof(registry)); + } - /// - /// Creates a registry of policies with keys. - /// This internal constructor exists solely to facilitate testing of the GetEnumerator() methods, which allow us to support collection initialisation syntax. - /// - /// a dictionary containing keys and policies used for testing. - internal PolicyRegistry(IDictionary registry) + private ConcurrentDictionary ThrowIfNotConcurrentImplementation() + { + if (_registry is ConcurrentDictionary concurrentRegistry) { - _registry = registry ?? throw new NullReferenceException(nameof(registry)); + return concurrentRegistry; } - private ConcurrentDictionary ThrowIfNotConcurrentImplementation() - { - if (_registry is ConcurrentDictionary concurrentRegistry) - { - return concurrentRegistry; - } - - throw new InvalidOperationException($"This {nameof(PolicyRegistry)} is not configured for concurrent operations. This exception should never be thrown in production code as the only public constructors create {nameof(PolicyRegistry)} instances of the correct form."); - } + throw new InvalidOperationException($"This {nameof(PolicyRegistry)} is not configured for concurrent operations. This exception should never be thrown in production code as the only public constructors create {nameof(PolicyRegistry)} instances of the correct form."); + } - /// - /// Total number of policies in the registry. - /// - public int Count => _registry.Count; + /// + /// Total number of policies in the registry. + /// + public int Count => _registry.Count; - /// - /// Adds a policy with the provided key and policy to the registry. - /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// is null. - /// A Policy with same already exists. - public void Add(string key, TPolicy policy) where TPolicy : IsPolicy => - _registry.Add(key, policy); + /// + /// Adds a policy with the provided key and policy to the registry. + /// + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// is null. + /// A Policy with same already exists. + public void Add(string key, TPolicy policy) where TPolicy : IsPolicy => + _registry.Add(key, policy); - /// - /// Adds a policy with the provided key and policy to the registry. - /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// True if Policy was added. False otherwise. - public bool TryAdd(string key, TPolicy policy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Adds a policy with the provided key and policy to the registry. + /// + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// True if Policy was added. False otherwise. + public bool TryAdd(string key, TPolicy policy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - return registry.TryAdd(key, policy); - } + return registry.TryAdd(key, policy); + } - /// - /// Gets of sets the with the specified key. - /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. - /// - /// The key of the policy to get or set. - /// is null. - /// The given key was not present in the registry. - /// The policy associated with the specified key. - public IsPolicy this[string key] - { - get => _registry[key]; - set => _registry[key] = value; - } + /// + /// Gets of sets the with the specified key. + /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. + /// + /// The key of the policy to get or set. + /// is null. + /// The given key was not present in the registry. + /// The policy associated with the specified key. + public IsPolicy this[string key] + { + get => _registry[key]; + set => _registry[key] = value; + } - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The type of Policy. - /// The policy stored in the registry under the given key. - /// is null. - /// The given key was not present in the registry. - public TPolicy Get(string key) where TPolicy : IsPolicy => - (TPolicy) _registry[key]; + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The type of Policy. + /// The policy stored in the registry under the given key. + /// is null. + /// The given key was not present in the registry. + public TPolicy Get(string key) where TPolicy : IsPolicy => + (TPolicy) _registry[key]; - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The key of the policy to get. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if Policy exists for the provided Key. False otherwise. - public bool TryGet(string key, out TPolicy policy) where TPolicy : IsPolicy - { - bool got = _registry.TryGetValue(key, out IsPolicy value); - policy = got ? (TPolicy)value : default; - return got; - } + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The key of the policy to get. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if Policy exists for the provided Key. False otherwise. + public bool TryGet(string key, out TPolicy policy) where TPolicy : IsPolicy + { + bool got = _registry.TryGetValue(key, out IsPolicy value); + policy = got ? (TPolicy)value : default; + return got; + } - /// - /// Removes all keys and policies from registry. - /// - public void Clear() => - _registry.Clear(); + /// + /// Removes all keys and policies from registry. + /// + public void Clear() => + _registry.Clear(); - /// - /// Determines whether the specified exists. - /// - /// The key to locate in the registry. - /// True if exists otherwise false. - /// is null. - public bool ContainsKey(string key) => - _registry.ContainsKey(key); + /// + /// Determines whether the specified exists. + /// + /// The key to locate in the registry. + /// True if exists otherwise false. + /// is null. + public bool ContainsKey(string key) => + _registry.ContainsKey(key); - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The of the policy to remove. - /// True if the policy is successfully removed. Otherwise false. - /// is null. - public bool Remove(string key) => - _registry.Remove(key); + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The of the policy to remove. + /// True if the policy is successfully removed. Otherwise false. + /// is null. + public bool Remove(string key) => + _registry.Remove(key); - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The of the policy to remove. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if the policy is successfully removed. Otherwise false. - public bool TryRemove(string key, out TPolicy policy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The of the policy to remove. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if the policy is successfully removed. Otherwise false. + public bool TryRemove(string key, out TPolicy policy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - bool got = registry.TryRemove(key, out IsPolicy value); - policy = got ? (TPolicy) value : default; - return got; - } + bool got = registry.TryRemove(key, out IsPolicy value); + policy = got ? (TPolicy) value : default; + return got; + } - /// - /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. - /// - /// - /// The key whose value is compared with comparisonPolicy, and possibly replaced. - /// The policy that replaces the value for the specified , if the comparison results in equality. - /// The policy that is compared to the existing policy at the specified key. - /// - public bool TryUpdate(string key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. + /// + /// + /// The key whose value is compared with comparisonPolicy, and possibly replaced. + /// The policy that replaces the value for the specified , if the comparison results in equality. + /// The policy that is compared to the existing policy at the specified key. + /// + public bool TryUpdate(string key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - return registry.TryUpdate(key, newPolicy, comparisonPolicy); - } + return registry.TryUpdate(key, newPolicy, comparisonPolicy); + } - /// - /// Adds a policy with the provided key and policy to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// The function used to generate a policy for the key - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy for the key as returned by policyFactory - /// if the key was not in the registry. - public TPolicy GetOrAdd(string key, Func policyFactory) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Adds a policy with the provided key and policy to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// The function used to generate a policy for the key + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy for the key as returned by policyFactory + /// if the key was not in the registry. + public TPolicy GetOrAdd(string key, Func policyFactory) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - return (TPolicy) registry.GetOrAdd(key, k => policyFactory(k)); - } + return (TPolicy) registry.GetOrAdd(key, k => policyFactory(k)); + } - /// - /// Adds a key/policy pair to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// the policy to be added, if the key does not already exist - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy if the key was not in the registry. - public TPolicy GetOrAdd(string key, TPolicy policy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Adds a key/policy pair to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// the policy to be added, if the key does not already exist + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy if the key was not in the registry. + public TPolicy GetOrAdd(string key, TPolicy policy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - return (TPolicy) registry.GetOrAdd(key, policy); - } + return (TPolicy) registry.GetOrAdd(key, policy); + } - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The function used to generate a policy for an absent key - /// The function used to generate a new policy for an existing key - /// based on the key's existing value - /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - public TPolicy AddOrUpdate(string key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The function used to generate a policy for an absent key + /// The function used to generate a new policy for an existing key + /// based on the key's existing value + /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + public TPolicy AddOrUpdate(string key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - return (TPolicy) registry.AddOrUpdate(key, k => addPolicyFactory(k), (k, e) => updatePolicyFactory(k, (TPolicy)e)); - } + return (TPolicy) registry.AddOrUpdate(key, k => addPolicyFactory(k), (k, e) => updatePolicyFactory(k, (TPolicy)e)); + } - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The policy to be added for an absent key - /// The function used to generate a new policy for an existing key based on - /// the key's existing value - /// The new policy for the key. This will be either be addPolicy (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - public TPolicy AddOrUpdate(string key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The policy to be added for an absent key + /// The function used to generate a new policy for an existing key based on + /// the key's existing value + /// The new policy for the key. This will be either be addPolicy (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + public TPolicy AddOrUpdate(string key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - return (TPolicy)registry.AddOrUpdate(key, addPolicy, (k, e) => updatePolicyFactory(k, (TPolicy)e)); - } + return (TPolicy)registry.AddOrUpdate(key, addPolicy, (k, e) => updatePolicyFactory(k, (TPolicy)e)); + } - /// Returns an enumerator that iterates through the policy objects in the . - /// An enumerator for the . - /// - /// The enumerator returned from the registry is safe to use concurrently with - /// reads and writes to the registry, however it does not represent a moment-in-time snapshot - /// of the registry's contents. The contents exposed through the enumerator may contain modifications - /// made to the registry after was called. - /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, - /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. - /// - public IEnumerator> GetEnumerator() => _registry.GetEnumerator(); + /// Returns an enumerator that iterates through the policy objects in the . + /// An enumerator for the . + /// + /// The enumerator returned from the registry is safe to use concurrently with + /// reads and writes to the registry, however it does not represent a moment-in-time snapshot + /// of the registry's contents. The contents exposed through the enumerator may contain modifications + /// made to the registry after was called. + /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, + /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. + /// + public IEnumerator> GetEnumerator() => _registry.GetEnumerator(); - /// Returns an enumerator that iterates through the policy objects in the . - /// An enumerator for the . - /// - /// The enumerator returned from the registry is safe to use concurrently with - /// reads and writes to the registry, however it does not represent a moment-in-time snapshot - /// of the registry's contents. The contents exposed through the enumerator may contain modifications - /// made to the registry after was called. - /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, - /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - } -} \ No newline at end of file + /// Returns an enumerator that iterates through the policy objects in the . + /// An enumerator for the . + /// + /// The enumerator returned from the registry is safe to use concurrently with + /// reads and writes to the registry, however it does not represent a moment-in-time snapshot + /// of the registry's contents. The contents exposed through the enumerator may contain modifications + /// made to the registry after was called. + /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, + /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); +} diff --git a/src/Polly/ResultPredicate.cs b/src/Polly/ResultPredicate.cs index c597981ff9c..08f7a14c072 100644 --- a/src/Polly/ResultPredicate.cs +++ b/src/Polly/ResultPredicate.cs @@ -1,11 +1,10 @@ -namespace Polly -{ - /// - /// A predicate that can be run against a passed result value of type . Predicates are used to define whether policies handle the given result. - /// - /// The passed result, against which to evaluate the predicate. - /// The type of results which this predicate can evaluate. - /// True if the passed matched the predicate; otherwise, false. +namespace Polly; - public delegate bool ResultPredicate(TResult result); -} \ No newline at end of file +/// +/// A predicate that can be run against a passed result value of type . Predicates are used to define whether policies handle the given result. +/// +/// The passed result, against which to evaluate the predicate. +/// The type of results which this predicate can evaluate. +/// True if the passed matched the predicate; otherwise, false. + +public delegate bool ResultPredicate(TResult result); diff --git a/src/Polly/ResultPredicates.cs b/src/Polly/ResultPredicates.cs index 1593148bac1..293ccfbb358 100644 --- a/src/Polly/ResultPredicates.cs +++ b/src/Polly/ResultPredicates.cs @@ -1,37 +1,36 @@ using System.Collections.Generic; using System.Linq; -namespace Polly +namespace Polly; + +/// +/// A collection of predicates used to define whether a policy handles a given value. +/// +public class ResultPredicates { + private List> _predicates; + + internal void Add(ResultPredicate predicate) + { + _predicates = _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.Add(predicate); + } + /// - /// A collection of predicates used to define whether a policy handles a given value. + /// Returns a bool indicating whether the passed value matched any predicates. /// - public class ResultPredicates + /// The value to assess against the predicates. + public bool AnyMatch(TResult result) { - private List> _predicates; - - internal void Add(ResultPredicate predicate) - { - _predicates = _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.Add(predicate); - } - - /// - /// Returns a bool indicating whether the passed value matched any predicates. - /// - /// The value to assess against the predicates. - public bool AnyMatch(TResult result) - { - if (_predicates == null) return false; - - return _predicates.Any(predicate => predicate(result)); - } - - /// - /// Specifies that no result-handling filters are applied or are required. - /// - public static readonly ResultPredicates None = new ResultPredicates(); + if (_predicates == null) return false; + + return _predicates.Any(predicate => predicate(result)); } -} \ No newline at end of file + /// + /// Specifies that no result-handling filters are applied or are required. + /// + public static readonly ResultPredicates None = new ResultPredicates(); +} + diff --git a/src/Polly/Retry/AsyncRetryEngine.cs b/src/Polly/Retry/AsyncRetryEngine.cs index 6a35f2ab267..dc8997e5732 100644 --- a/src/Polly/Retry/AsyncRetryEngine.cs +++ b/src/Polly/Retry/AsyncRetryEngine.cs @@ -4,87 +4,86 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Retry +namespace Polly.Retry; + +internal static class AsyncRetryEngine { - internal static class AsyncRetryEngine + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldRetryExceptionPredicates, + ResultPredicates shouldRetryResultPredicates, + Func, TimeSpan, int, Context, Task> onRetryAsync, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null, + bool continueOnCapturedContext = false) { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldRetryExceptionPredicates, - ResultPredicates shouldRetryResultPredicates, - Func, TimeSpan, int, Context, Task> onRetryAsync, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null, - bool continueOnCapturedContext = false) - { - int tryCount = 0; - IEnumerator sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); + int tryCount = 0; + IEnumerator sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); - try + try + { + while (true) { - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - bool canRetry; - DelegateResult outcome; + bool canRetry; + DelegateResult outcome; - try - { - TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - - if (!shouldRetryResultPredicates.AnyMatch(result)) - { - return result; - } + try + { + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); + if (!shouldRetryResultPredicates.AnyMatch(result)) + { + return result; + } - if (!canRetry) - { - return result; - } + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); - outcome = new DelegateResult(result); - } - catch (Exception ex) + if (!canRetry) { - Exception handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } + return result; + } - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); + outcome = new DelegateResult(result); + } + catch (Exception ex) + { + Exception handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - if (!canRetry) - { - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); - throw; - } + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); - outcome = new DelegateResult(handledException); + if (!canRetry) + { + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; } - if (tryCount < int.MaxValue) { tryCount++; } + outcome = new DelegateResult(handledException); + } - TimeSpan waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); + if (tryCount < int.MaxValue) { tryCount++; } - await onRetryAsync(outcome, waitDuration, tryCount, context).ConfigureAwait(continueOnCapturedContext); + TimeSpan waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); - if (waitDuration > TimeSpan.Zero) - { - await SystemClock.SleepAsync(waitDuration, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + await onRetryAsync(outcome, waitDuration, tryCount, context).ConfigureAwait(continueOnCapturedContext); + + if (waitDuration > TimeSpan.Zero) + { + await SystemClock.SleepAsync(waitDuration, cancellationToken).ConfigureAwait(continueOnCapturedContext); } } - finally - { - sleepDurationsEnumerator?.Dispose(); - } + } + finally + { + sleepDurationsEnumerator?.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Polly/Retry/AsyncRetryPolicy.cs b/src/Polly/Retry/AsyncRetryPolicy.cs index 2abd3f4cd1d..933dc2dd78d 100644 --- a/src/Polly/Retry/AsyncRetryPolicy.cs +++ b/src/Polly/Retry/AsyncRetryPolicy.cs @@ -4,96 +4,95 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Retry -{ - /// - /// A retry policy that can be applied to asynchronous delegates. - /// - public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy - { - private readonly Func _onRetryAsync; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func _sleepDurationProvider; +namespace Polly.Retry; - internal AsyncRetryPolicy( - PolicyBuilder policyBuilder, - Func onRetryAsync, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func sleepDurationProvider = null - ) - : base(policyBuilder) - { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); - } +/// +/// A retry policy that can be applied to asynchronous delegates. +/// +public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy +{ + private readonly Func _onRetryAsync; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func _sleepDurationProvider; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncRetryEngine.ImplementationAsync( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, timespan, retryCount, ctx) => _onRetryAsync(outcome.Exception, timespan, retryCount, ctx), - _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider != null - ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) - : (Func, Context, TimeSpan>)null, - continueOnCapturedContext - ); - } + internal AsyncRetryPolicy( + PolicyBuilder policyBuilder, + Func onRetryAsync, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func sleepDurationProvider = null + ) + : base(policyBuilder) + { + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); } - /// - /// A retry policy that can be applied to asynchronous delegates returning a value of type . - /// - public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) { - private readonly Func, TimeSpan, int, Context, Task> _onRetryAsync; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func, Context, TimeSpan> _sleepDurationProvider; + return AsyncRetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + (outcome, timespan, retryCount, ctx) => _onRetryAsync(outcome.Exception, timespan, retryCount, ctx), + _permittedRetryCount, + _sleepDurationsEnumerable, + _sleepDurationProvider != null + ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) + : (Func, Context, TimeSpan>)null, + continueOnCapturedContext + ); + } +} - internal AsyncRetryPolicy( - PolicyBuilder policyBuilder, - Func, TimeSpan, int, Context, Task> onRetryAsync, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null - ) - : base(policyBuilder) - { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); - } +/// +/// A retry policy that can be applied to asynchronous delegates returning a value of type . +/// +public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy +{ + private readonly Func, TimeSpan, int, Context, Task> _onRetryAsync; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func, Context, TimeSpan> _sleepDurationProvider; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncRetryEngine.ImplementationAsync( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _onRetryAsync, - _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider, - continueOnCapturedContext - ); + internal AsyncRetryPolicy( + PolicyBuilder policyBuilder, + Func, TimeSpan, int, Context, Task> onRetryAsync, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null + ) + : base(policyBuilder) + { + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); } + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncRetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onRetryAsync, + _permittedRetryCount, + _sleepDurationsEnumerable, + _sleepDurationProvider, + continueOnCapturedContext + ); } diff --git a/src/Polly/Retry/AsyncRetrySyntax.cs b/src/Polly/Retry/AsyncRetrySyntax.cs index 8b9449a4aac..b494e888578 100644 --- a/src/Polly/Retry/AsyncRetrySyntax.cs +++ b/src/Polly/Retry/AsyncRetrySyntax.cs @@ -4,1106 +4,1105 @@ using System.Threading.Tasks; using Polly.Retry; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a . +/// +public static class AsyncRetrySyntax { /// - /// Fluent API for defining a . + /// Builds an that will retry once. /// - public static class AsyncRetrySyntax + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) + => policyBuilder.RetryAsync(1); + + /// + /// Builds an that will retry times. + /// + /// The policy builder. + /// The retry count. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) { - /// - /// Builds an that will retry once. - /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) - => policyBuilder.RetryAsync(1); - - /// - /// Builds an that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) - { - Action doNothing = (_, _, _) => { }; - - return policyBuilder.RetryAsync(retryCount, onRetry: doNothing); - } - - /// - /// Builds an that will retry once - /// calling on retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.RetryAsync(1, + Action doNothing = (_, _, _) => { }; + + return policyBuilder.RetryAsync(retryCount, onRetry: doNothing); + } + + /// + /// Builds an that will retry once + /// calling on retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.RetryAsync(1, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); + ); - /// - /// Builds an that will retry once - /// calling on retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + /// + /// Builds an that will retry once + /// calling on retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry once - /// calling on retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.RetryAsync(1, onRetry); - - /// - /// Builds an that will retry once - /// calling on retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync); - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + ); + } + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry once + /// calling on retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.RetryAsync(1, onRetry); + + /// + /// Builds an that will retry once + /// calling on retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync); + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), - retryCount ); - } - - /// - /// Builds an that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) - { - Action doNothing = _ => { }; - - return policyBuilder.RetryForeverAsync(doNothing); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + } + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), + retryCount + ); + } + + /// + /// Builds an that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) + { + Action doNothing = _ => { }; + + return policyBuilder.RetryForeverAsync(doNothing); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (Exception outcome, Context _) => onRetry(outcome) + onRetryAsync: async (Exception outcome, Context _) => onRetry(outcome) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (Exception outcome, Context _) => onRetryAsync(outcome)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (Exception outcome, Context _) => onRetryAsync(outcome)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) + onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) - ); - } - - /// - /// Builds an that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) + ); + } + + /// + /// Builds an that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) + onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) + onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - IEnumerable sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + IEnumerable sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryAsync( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryAsync( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationProvider: sleepDurationProvider + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) + onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) + onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetry(exception, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, i, timespan, _) => onRetry(exception, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetryAsync(exception, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, i, timespan, _) => onRetryAsync(exception, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetry(exception, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, i, timespan, _) => onRetry(exception, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetryAsync(exception, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, i, timespan, _) => onRetryAsync(exception, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (exception, timespan, ctx) => onRetry(exception, timespan, ctx) + async (exception, timespan, ctx) => onRetry(exception, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (exception, i, timespan, ctx) => onRetry(exception, i, timespan, ctx) + async (exception, i, timespan, ctx) => onRetry(exception, i, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), - sleepDurationProvider: sleepDurationProvider - ); - } + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider + ); } } diff --git a/src/Polly/Retry/AsyncRetryTResultSyntax.cs b/src/Polly/Retry/AsyncRetryTResultSyntax.cs index 8dc131281c0..a50abd1714c 100644 --- a/src/Polly/Retry/AsyncRetryTResultSyntax.cs +++ b/src/Polly/Retry/AsyncRetryTResultSyntax.cs @@ -4,1104 +4,1103 @@ using System.Threading.Tasks; using Polly.Retry; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining an . +/// +public static class AsyncRetryTResultSyntax { /// - /// Fluent API for defining an . + /// Builds an that will retry once. /// - public static class AsyncRetryTResultSyntax + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) + => policyBuilder.RetryAsync(1); + + /// + /// Builds an that will retry times. + /// + /// The policy builder. + /// The retry count. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) { - /// - /// Builds an that will retry once. - /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) - => policyBuilder.RetryAsync(1); - - /// - /// Builds an that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) - { - Action, int> doNothing = (_, _) => { }; - - return policyBuilder.RetryAsync(retryCount, doNothing); - } - - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) - => policyBuilder.RetryAsync(1, + Action, int> doNothing = (_, _) => { }; + + return policyBuilder.RetryAsync(retryCount, doNothing); + } + + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) + => policyBuilder.RetryAsync(1, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); + ); - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - => policyBuilder.RetryAsync(1, onRetry); - - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync); - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + ); + } + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + => policyBuilder.RetryAsync(1, onRetry); + + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync); + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Context, Task> onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), - retryCount ); - } - - /// - /// Builds an that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) - { - Action> doNothing = _ => { }; - - return policyBuilder.RetryForeverAsync(doNothing); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + } + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Context, Task> onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), + retryCount + ); + } + + /// + /// Builds an that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) + { + Action> doNothing = _ => { }; + + return policyBuilder.RetryForeverAsync(doNothing); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (DelegateResult outcome, Context _) => onRetry(outcome) + onRetryAsync: async (DelegateResult outcome, Context _) => onRetry(outcome) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (DelegateResult outcome, Context _) => onRetryAsync(outcome)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (DelegateResult outcome, Context _) => onRetryAsync(outcome)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) + onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) - ); - } - - /// - /// Builds an that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) + ); + } + + /// + /// Builds an that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) + onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) + onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - IEnumerable sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + IEnumerable sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryAsync( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), result of previous execution, and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryAsync( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), result of previous execution, and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationProvider: sleepDurationProvider + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) + onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) + onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) #pragma warning restore 1998 - ); + ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan, Context> doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, timespan, _) => onRetry(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, i, timespan, _) => onRetry(outcome, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, timespan, _) => onRetryAsync(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, i, timespan, _) => onRetryAsync(outcome, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan, Context> doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, timespan, _) => onRetry(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, i, timespan, _) => onRetry(outcome, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, timespan, _) => onRetryAsync(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, i, timespan, _) => onRetryAsync(outcome, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (outcome, timespan, ctx) => onRetry(outcome, timespan, ctx) + async (outcome, timespan, ctx) => onRetry(outcome, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (outcome, i, timespan, ctx) => onRetry(outcome, i, timespan, ctx) + async (outcome, i, timespan, ctx) => onRetry(outcome, i, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), - sleepDurationProvider: sleepDurationProvider - ); - } + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider + ); } } diff --git a/src/Polly/Retry/IRetryPolicy.cs b/src/Polly/Retry/IRetryPolicy.cs index 89daedbbc6d..7b205d7a425 100644 --- a/src/Polly/Retry/IRetryPolicy.cs +++ b/src/Polly/Retry/IRetryPolicy.cs @@ -1,18 +1,17 @@ -namespace Polly.Retry -{ - /// - /// Defines properties and methods common to all Retry policies. - /// +namespace Polly.Retry; - public interface IRetryPolicy : IsPolicy - { - } +/// +/// Defines properties and methods common to all Retry policies. +/// - /// - /// Defines properties and methods common to all Retry policies generic-typed for executions returning results of type . - /// - public interface IRetryPolicy : IRetryPolicy - { +public interface IRetryPolicy : IsPolicy +{ +} + +/// +/// Defines properties and methods common to all Retry policies generic-typed for executions returning results of type . +/// +public interface IRetryPolicy : IRetryPolicy +{ - } } diff --git a/src/Polly/Retry/RetryEngine.cs b/src/Polly/Retry/RetryEngine.cs index d7e6703d831..80b9873208e 100644 --- a/src/Polly/Retry/RetryEngine.cs +++ b/src/Polly/Retry/RetryEngine.cs @@ -3,87 +3,86 @@ using System.Threading; using Polly.Utilities; -namespace Polly.Retry +namespace Polly.Retry; + +internal static class RetryEngine { - internal static class RetryEngine + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldRetryExceptionPredicates, + ResultPredicates shouldRetryResultPredicates, + Action, TimeSpan, int, Context> onRetry, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null) { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldRetryExceptionPredicates, - ResultPredicates shouldRetryResultPredicates, - Action, TimeSpan, int, Context> onRetry, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null) - { - int tryCount = 0; - IEnumerator sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); + int tryCount = 0; + IEnumerator sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); - try + try + { + while (true) { - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - bool canRetry; - DelegateResult outcome; + bool canRetry; + DelegateResult outcome; - try - { - TResult result = action(context, cancellationToken); - - if (!shouldRetryResultPredicates.AnyMatch(result)) - { - return result; - } - - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); - - if (!canRetry) - { - return result; - } + try + { + TResult result = action(context, cancellationToken); - outcome = new DelegateResult(result); - } - catch (Exception ex) + if (!shouldRetryResultPredicates.AnyMatch(result)) { - Exception handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; - } - - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); - - if (!canRetry) - { - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); - throw; - } + return result; + } - outcome = new DelegateResult(handledException); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); + + if (!canRetry) + { + return result; } - if (tryCount < int.MaxValue) { tryCount++; } + outcome = new DelegateResult(result); + } + catch (Exception ex) + { + Exception handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - TimeSpan waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); - - onRetry(outcome, waitDuration, tryCount, context); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerator == null || sleepDurationsEnumerator.MoveNext()); - if (waitDuration > TimeSpan.Zero) + if (!canRetry) { - SystemClock.Sleep(waitDuration, cancellationToken); + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; } + + outcome = new DelegateResult(handledException); } + if (tryCount < int.MaxValue) { tryCount++; } + + TimeSpan waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); + + onRetry(outcome, waitDuration, tryCount, context); + + if (waitDuration > TimeSpan.Zero) + { + SystemClock.Sleep(waitDuration, cancellationToken); + } } - finally - { - sleepDurationsEnumerator?.Dispose(); - } + + } + finally + { + sleepDurationsEnumerator?.Dispose(); } } } diff --git a/src/Polly/Retry/RetryPolicy.cs b/src/Polly/Retry/RetryPolicy.cs index 6a5367dee1f..7ae2f297117 100644 --- a/src/Polly/Retry/RetryPolicy.cs +++ b/src/Polly/Retry/RetryPolicy.cs @@ -3,88 +3,87 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Retry -{ - /// - /// A retry policy that can be applied to synchronous delegates. - /// - public class RetryPolicy : Policy, IRetryPolicy - { - private readonly Action _onRetry; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func _sleepDurationProvider; - - internal RetryPolicy( - PolicyBuilder policyBuilder, - Action onRetry, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func sleepDurationProvider = null - ) - : base(policyBuilder) - { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); - } +namespace Polly.Retry; - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RetryEngine.Implementation( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, timespan, retryCount, ctx) => _onRetry(outcome.Exception, timespan, retryCount, ctx), - _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider != null - ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) - : (Func, Context, TimeSpan>)null - ); - } +/// +/// A retry policy that can be applied to synchronous delegates. +/// +public class RetryPolicy : Policy, IRetryPolicy +{ + private readonly Action _onRetry; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func _sleepDurationProvider; - /// - /// A retry policy that can be applied to synchronous delegates returning a value of type . - /// - public class RetryPolicy : Policy, IRetryPolicy + internal RetryPolicy( + PolicyBuilder policyBuilder, + Action onRetry, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func sleepDurationProvider = null + ) + : base(policyBuilder) { - private readonly Action, TimeSpan, int, Context> _onRetry; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func, Context, TimeSpan> _sleepDurationProvider; - - internal RetryPolicy( - PolicyBuilder policyBuilder, - Action, TimeSpan, int, Context> onRetry, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null - ) - : base(policyBuilder) - { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); - } + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); + } - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RetryEngine.Implementation( - action, - context, + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RetryEngine.Implementation( + action, + context, cancellationToken, ExceptionPredicates, - ResultPredicates, - _onRetry, + ResultPredicates.None, + (outcome, timespan, retryCount, ctx) => _onRetry(outcome.Exception, timespan, retryCount, ctx), _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider + _sleepDurationsEnumerable, + _sleepDurationProvider != null + ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) + : (Func, Context, TimeSpan>)null ); +} + +/// +/// A retry policy that can be applied to synchronous delegates returning a value of type . +/// +public class RetryPolicy : Policy, IRetryPolicy +{ + private readonly Action, TimeSpan, int, Context> _onRetry; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func, Context, TimeSpan> _sleepDurationProvider; + + internal RetryPolicy( + PolicyBuilder policyBuilder, + Action, TimeSpan, int, Context> onRetry, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null + ) + : base(policyBuilder) + { + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); } -} \ No newline at end of file + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RetryEngine.Implementation( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onRetry, + _permittedRetryCount, + _sleepDurationsEnumerable, + _sleepDurationProvider + ); +} diff --git a/src/Polly/Retry/RetrySyntax.cs b/src/Polly/Retry/RetrySyntax.cs index 48fef596e28..a440a4e9d39 100644 --- a/src/Polly/Retry/RetrySyntax.cs +++ b/src/Polly/Retry/RetrySyntax.cs @@ -3,632 +3,631 @@ using System.Linq; using Polly.Retry; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Retry . +/// +public static class RetrySyntax { /// - /// Fluent API for defining a Retry . + /// Builds a that will retry once. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder) + => policyBuilder.Retry(1); + + /// + /// Builds a that will retry times. /// - public static class RetrySyntax + /// The policy builder. + /// The retry count. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) { - /// - /// Builds a that will retry once. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder) - => policyBuilder.Retry(1); - - /// - /// Builds a that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) - { - Action doNothing = (_, _) => { }; - - return policyBuilder.Retry(retryCount, doNothing); - } - - /// - /// Builds a that will retry once - /// calling on retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry once - /// calling on retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + Action doNothing = (_, _) => { }; - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx), - retryCount); - } - - /// - /// Builds a that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) - { - Action doNothing = _ => { }; - - return policyBuilder.RetryForever(doNothing); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((Exception outcome, Context _) => onRetry(outcome)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetry(outcome, ctx) - ); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + return policyBuilder.Retry(retryCount, doNothing); + } - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx) - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, _) => onRetry(outcome, span) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - var sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); + /// + /// Builds a that will retry once + /// calling on retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.Retry(1, onRetry); - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetry( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetry(sleepDurations, doNothing); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the raised exception, current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the raised exception, current sleep duration, retry count and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + /// + /// Builds a that will retry times + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry once + /// calling on retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.Retry(1, onRetry); + + /// + /// Builds a that will retry times + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx), + retryCount); + } + + /// + /// Builds a that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) + { + Action doNothing = _ => { }; + + return policyBuilder.RetryForever(doNothing); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((Exception outcome, Context _) => onRetry(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); return new RetryPolicy( policyBuilder, - onRetry, - sleepDurationsEnumerable: sleepDurations + (outcome, _, _, ctx) => onRetry(outcome, ctx) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetry(exception, timespan) + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx) + ); + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, _) => onRetry(outcome, span) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _, _) => sleepDurationProvider(retryCount), - (exception, i, timespan, _) => onRetry(exception, i, timespan) + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + var sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new RetryPolicy( + policyBuilder, + onRetry, + retryCount, + sleepDurationsEnumerable: sleepDurations ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + } - return new RetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetry( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); return new RetryPolicy( policyBuilder, - (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), + onRetry, + retryCount, sleepDurationProvider: sleepDurationProvider ); - } } -} \ No newline at end of file + + /// + /// Builds a that will wait and retry as many times as there are provided + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetry(sleepDurations, doNothing); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the raised exception, current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the raised exception, current sleep duration, retry count and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + onRetry, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetry(exception, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _, _) => sleepDurationProvider(retryCount), + (exception, i, timespan, _) => onRetry(exception, i, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider + ); + } +} diff --git a/src/Polly/Retry/RetryTResultSyntax.cs b/src/Polly/Retry/RetryTResultSyntax.cs index 25e599b40d1..0183dbd6bbd 100644 --- a/src/Polly/Retry/RetryTResultSyntax.cs +++ b/src/Polly/Retry/RetryTResultSyntax.cs @@ -3,673 +3,672 @@ using Polly.Retry; using System.Linq; -namespace Polly +namespace Polly; + +/// +/// Fluent API for defining a Retry . +/// +public static class RetryTResultSyntax { /// - /// Fluent API for defining a Retry . + /// Builds a that will retry once. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder) + => policyBuilder.Retry(1); + + /// + /// Builds a that will retry times. /// - public static class RetryTResultSyntax + /// The policy builder. + /// The retry count. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) { - /// - /// Builds a that will retry once. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder) - => policyBuilder.Retry(1); - - /// - /// Builds a that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) - { - Action, int> doNothing = (_, _) => { }; - - return policyBuilder.Retry(retryCount, doNothing); - } - - /// - /// Builds a that will retry once - /// calling on retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int> onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry once - /// calling on retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx), - retryCount); - } - - /// - /// Builds a that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) - { - Action> doNothing = _ => { }; - - return policyBuilder.RetryForever(doNothing); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((DelegateResult outcome, Context _) => onRetry(outcome)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetry(outcome, ctx) - ); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx) - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, _) => onRetry(outcome, span) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - var sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); - - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - => policyBuilder.WaitAndRetry( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry - ); + Action, int> doNothing = (_, _) => { }; - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider) - { - Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetry(sleepDurations, doNothing); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the handled exception or result, current sleep duration, retry count and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - onRetry, - sleepDurationsEnumerable: sleepDurations + return policyBuilder.Retry(retryCount, doNothing); + } + + /// + /// Builds a that will retry once + /// calling on retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int> onRetry) + => policyBuilder.Retry(1, onRetry); + + /// + /// Builds a that will retry times + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry once + /// calling on retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + => policyBuilder.Retry(1, onRetry); + + /// + /// Builds a that will retry times + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx), + retryCount); + } + + /// + /// Builds a that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) + { + Action> doNothing = _ => { }; + + return policyBuilder.RetryForever(doNothing); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((DelegateResult outcome, Context _) => onRetry(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, _, ctx) => onRetry(outcome, ctx) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan, Context> doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetry(exception, timespan) + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx) + ); + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, _) => onRetry(outcome, span) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _, _) => sleepDurationProvider(retryCount), - (outcome, i, timespan, _) => onRetry(outcome, i, timespan) + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + var sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new RetryPolicy( + policyBuilder, + onRetry, + retryCount, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + => policyBuilder.WaitAndRetry( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider) + { + Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + onRetry, + retryCount, + sleepDurationProvider: sleepDurationProvider + ); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetry(sleepDurations, doNothing); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the handled exception or result, current sleep duration, retry count and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + onRetry, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan, Context> doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetry(exception, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _, _) => sleepDurationProvider(retryCount), + (outcome, i, timespan, _) => onRetry(outcome, i, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), - sleepDurationProvider: sleepDurationProvider - ); - } } -} \ No newline at end of file +} diff --git a/src/Polly/Timeout/AsyncTimeoutEngine.cs b/src/Polly/Timeout/AsyncTimeoutEngine.cs index fff1ebecc5f..8abf0345a61 100644 --- a/src/Polly/Timeout/AsyncTimeoutEngine.cs +++ b/src/Polly/Timeout/AsyncTimeoutEngine.cs @@ -3,78 +3,77 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Timeout +namespace Polly.Timeout; + +internal static class AsyncTimeoutEngine { - internal static class AsyncTimeoutEngine + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync, + bool continueOnCapturedContext) { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync, - bool continueOnCapturedContext) - { - cancellationToken.ThrowIfCancellationRequested(); - TimeSpan timeout = timeoutProvider(context); + cancellationToken.ThrowIfCancellationRequested(); + TimeSpan timeout = timeoutProvider(context); - using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource()) + { + using (CancellationTokenSource combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { - using (CancellationTokenSource combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) - { - Task actionTask = null; - CancellationToken combinedToken = combinedTokenSource.Token; + Task actionTask = null; + CancellationToken combinedToken = combinedTokenSource.Token; - try + try + { + if (timeoutStrategy == TimeoutStrategy.Optimistic) { - if (timeoutStrategy == TimeoutStrategy.Optimistic) - { - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - return await action(context, combinedToken).ConfigureAwait(continueOnCapturedContext); - } + SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); + return await action(context, combinedToken).ConfigureAwait(continueOnCapturedContext); + } - // else: timeoutStrategy == TimeoutStrategy.Pessimistic + // else: timeoutStrategy == TimeoutStrategy.Pessimistic - Task timeoutTask = timeoutCancellationTokenSource.Token.AsTask(); + Task timeoutTask = timeoutCancellationTokenSource.Token.AsTask(); - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); + SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - actionTask = action(context, combinedToken); + actionTask = action(context, combinedToken); - return await (await Task.WhenAny(actionTask, timeoutTask).ConfigureAwait(continueOnCapturedContext)).ConfigureAwait(continueOnCapturedContext); + return await (await Task.WhenAny(actionTask, timeoutTask).ConfigureAwait(continueOnCapturedContext)).ConfigureAwait(continueOnCapturedContext); - } - catch (Exception ex) + } + catch (Exception ex) + { + // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) + // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. + if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) { - // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) - // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. - if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) - { - await onTimeoutAsync(context, timeout, actionTask, ex).ConfigureAwait(continueOnCapturedContext); - throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", ex); - } - - throw; + await onTimeoutAsync(context, timeout, actionTask, ex).ConfigureAwait(continueOnCapturedContext); + throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", ex); } + + throw; } } } + } - private static Task AsTask(this CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); + private static Task AsTask(this CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); - // A generalised version of this method would include a hotpath returning a canceled task (rather than setting up a registration) if (cancellationToken.IsCancellationRequested) on entry. This is omitted, since we only start the timeout countdown in the token _after calling this method. + // A generalised version of this method would include a hotpath returning a canceled task (rather than setting up a registration) if (cancellationToken.IsCancellationRequested) on entry. This is omitted, since we only start the timeout countdown in the token _after calling this method. - IDisposable registration = null; - registration = cancellationToken.Register(() => - { - tcs.TrySetCanceled(); - registration?.Dispose(); - }, useSynchronizationContext: false); + IDisposable registration = null; + registration = cancellationToken.Register(() => + { + tcs.TrySetCanceled(); + registration?.Dispose(); + }, useSynchronizationContext: false); - return tcs.Task; - } + return tcs.Task; } } diff --git a/src/Polly/Timeout/AsyncTimeoutPolicy.cs b/src/Polly/Timeout/AsyncTimeoutPolicy.cs index d139b9c4a07..2f54da39504 100644 --- a/src/Polly/Timeout/AsyncTimeoutPolicy.cs +++ b/src/Polly/Timeout/AsyncTimeoutPolicy.cs @@ -3,81 +3,80 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Timeout -{ - /// - /// A timeout policy which can be applied to async delegates. - /// - public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy - { - private readonly Func _timeoutProvider; - private readonly TimeoutStrategy _timeoutStrategy; - private readonly Func _onTimeoutAsync; +namespace Polly.Timeout; - internal AsyncTimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync - ) - { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); - } +/// +/// A timeout policy which can be applied to async delegates. +/// +public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy +{ + private readonly Func _timeoutProvider; + private readonly TimeoutStrategy _timeoutStrategy; + private readonly Func _onTimeoutAsync; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncTimeoutEngine.ImplementationAsync( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeoutAsync, - continueOnCapturedContext); - } + internal AsyncTimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync + ) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); } - /// - /// A timeout policy which can be applied to async delegates. - /// - /// The return type of delegates which may be executed through the policy. - public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) { - private Func _timeoutProvider; - private TimeoutStrategy _timeoutStrategy; - private Func _onTimeoutAsync; + return AsyncTimeoutEngine.ImplementationAsync( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeoutAsync, + continueOnCapturedContext); + } +} - internal AsyncTimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync) - { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); - } +/// +/// A timeout policy which can be applied to async delegates. +/// +/// The return type of delegates which may be executed through the policy. +public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy +{ + private Func _timeoutProvider; + private TimeoutStrategy _timeoutStrategy; + private Func _onTimeoutAsync; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncTimeoutEngine.ImplementationAsync( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeoutAsync, - continueOnCapturedContext); + internal AsyncTimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); } -} \ No newline at end of file + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncTimeoutEngine.ImplementationAsync( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeoutAsync, + continueOnCapturedContext); +} diff --git a/src/Polly/Timeout/AsyncTimeoutSyntax.cs b/src/Polly/Timeout/AsyncTimeoutSyntax.cs index f8babf653dc..be177da0e65 100644 --- a/src/Polly/Timeout/AsyncTimeoutSyntax.cs +++ b/src/Polly/Timeout/AsyncTimeoutSyntax.cs @@ -3,389 +3,388 @@ using Polly.Timeout; using Polly.Utilities; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// The policy instance. + /// seconds;Value must be greater than zero. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// seconds;Value must be greater than zero. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// seconds;Value must be greater than zero. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync) { - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// The policy instance. - /// seconds;Value must be greater than zero. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// seconds;Value must be greater than zero. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// seconds;Value must be greater than zero. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return new AsyncTimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeoutAsync - ); - } + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return new AsyncTimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeoutAsync + ); } } diff --git a/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs b/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs index edff08a0ad3..0caf403a8bd 100644 --- a/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs +++ b/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs @@ -2,383 +2,382 @@ using System.Threading.Tasks; using Polly.Timeout; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) { - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return new AsyncTimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeoutAsync - ); - } + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return new AsyncTimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeoutAsync + ); } } diff --git a/src/Polly/Timeout/ITimeoutPolicy.cs b/src/Polly/Timeout/ITimeoutPolicy.cs index bdf26a02be4..1dc955573ee 100644 --- a/src/Polly/Timeout/ITimeoutPolicy.cs +++ b/src/Polly/Timeout/ITimeoutPolicy.cs @@ -1,18 +1,17 @@ -namespace Polly.Timeout -{ - /// - /// Defines properties and methods common to all Timeout policies. - /// +namespace Polly.Timeout; - public interface ITimeoutPolicy : IsPolicy - { - } +/// +/// Defines properties and methods common to all Timeout policies. +/// - /// - /// Defines properties and methods common to all Timeout policies generic-typed for executions returning results of type . - /// - public interface ITimeoutPolicy : ITimeoutPolicy - { +public interface ITimeoutPolicy : IsPolicy +{ +} + +/// +/// Defines properties and methods common to all Timeout policies generic-typed for executions returning results of type . +/// +public interface ITimeoutPolicy : ITimeoutPolicy +{ - } } diff --git a/src/Polly/Timeout/TimeoutEngine.cs b/src/Polly/Timeout/TimeoutEngine.cs index ae6c8bb81b6..9371fc100e0 100644 --- a/src/Polly/Timeout/TimeoutEngine.cs +++ b/src/Polly/Timeout/TimeoutEngine.cs @@ -4,69 +4,68 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Timeout +namespace Polly.Timeout; + +internal static class TimeoutEngine { - internal static class TimeoutEngine + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) - { - cancellationToken.ThrowIfCancellationRequested(); - TimeSpan timeout = timeoutProvider(context); + cancellationToken.ThrowIfCancellationRequested(); + TimeSpan timeout = timeoutProvider(context); - using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource()) + { + using (CancellationTokenSource combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { - using (CancellationTokenSource combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) - { - CancellationToken combinedToken = combinedTokenSource.Token; + CancellationToken combinedToken = combinedTokenSource.Token; - Task actionTask = null; - try + Task actionTask = null; + try + { + if (timeoutStrategy == TimeoutStrategy.Optimistic) { - if (timeoutStrategy == TimeoutStrategy.Optimistic) - { - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - return action(context, combinedToken); - } - - // else: timeoutStrategy == TimeoutStrategy.Pessimistic - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); + return action(context, combinedToken); + } + + // else: timeoutStrategy == TimeoutStrategy.Pessimistic - actionTask = Task.Run(() => - action(context, combinedToken) // cancellation token here allows the user delegate to react to cancellation: possibly clear up; then throw an OperationCanceledException. - , combinedToken); // cancellation token here only allows Task.Run() to not begin the passed delegate at all, if cancellation occurs prior to invoking the delegate. - try - { - actionTask.Wait(timeoutCancellationTokenSource.Token); // cancellation token here cancels the Wait() and causes it to throw, but does not cancel actionTask. We use only timeoutCancellationTokenSource.Token here, not combinedToken. If we allowed the user's cancellation token to cancel the Wait(), in this pessimistic scenario where the user delegate may not observe that cancellation, that would create a no-longer-observed task. That task could in turn later fault before completing, risking an UnobservedTaskException. - } - catch (AggregateException ex) when (ex.InnerExceptions.Count == 1) // Issue #270. Unwrap extra AggregateException caused by the way pessimistic timeout policy for synchronous executions is necessarily constructed. - { - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); - } + SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - return actionTask.Result; + actionTask = Task.Run(() => + action(context, combinedToken) // cancellation token here allows the user delegate to react to cancellation: possibly clear up; then throw an OperationCanceledException. + , combinedToken); // cancellation token here only allows Task.Run() to not begin the passed delegate at all, if cancellation occurs prior to invoking the delegate. + try + { + actionTask.Wait(timeoutCancellationTokenSource.Token); // cancellation token here cancels the Wait() and causes it to throw, but does not cancel actionTask. We use only timeoutCancellationTokenSource.Token here, not combinedToken. If we allowed the user's cancellation token to cancel the Wait(), in this pessimistic scenario where the user delegate may not observe that cancellation, that would create a no-longer-observed task. That task could in turn later fault before completing, risking an UnobservedTaskException. } - catch (Exception ex) + catch (AggregateException ex) when (ex.InnerExceptions.Count == 1) // Issue #270. Unwrap extra AggregateException caused by the way pessimistic timeout policy for synchronous executions is necessarily constructed. { - // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) - // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. - if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) - { - onTimeout(context, timeout, actionTask, ex); - throw new TimeoutRejectedException("The delegate executed through TimeoutPolicy did not complete within the timeout.", ex); - } + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + } - throw; + return actionTask.Result; + } + catch (Exception ex) + { + // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) + // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. + if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) + { + onTimeout(context, timeout, actionTask, ex); + throw new TimeoutRejectedException("The delegate executed through TimeoutPolicy did not complete within the timeout.", ex); } + + throw; } } } - } -} \ No newline at end of file + +} diff --git a/src/Polly/Timeout/TimeoutPolicy.cs b/src/Polly/Timeout/TimeoutPolicy.cs index 9b3b0470f10..45098c040a3 100644 --- a/src/Polly/Timeout/TimeoutPolicy.cs +++ b/src/Polly/Timeout/TimeoutPolicy.cs @@ -3,66 +3,65 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Timeout -{ - /// - /// A timeout policy which can be applied to delegates. - /// - public class TimeoutPolicy : Policy, ITimeoutPolicy - { - private Func _timeoutProvider; - private TimeoutStrategy _timeoutStrategy; - private Action _onTimeout; +namespace Polly.Timeout; - internal TimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) - { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); - } +/// +/// A timeout policy which can be applied to delegates. +/// +public class TimeoutPolicy : Policy, ITimeoutPolicy +{ + private Func _timeoutProvider; + private TimeoutStrategy _timeoutStrategy; + private Action _onTimeout; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => TimeoutEngine.Implementation( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeout); + internal TimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); } - /// - /// A timeout policy which can be applied to delegates returning a value of type . - /// - public class TimeoutPolicy : Policy, ITimeoutPolicy - { - private Func _timeoutProvider; - private TimeoutStrategy _timeoutStrategy; - private Action _onTimeout; + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => TimeoutEngine.Implementation( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeout); +} - internal TimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) - { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); - } +/// +/// A timeout policy which can be applied to delegates returning a value of type . +/// +public class TimeoutPolicy : Policy, ITimeoutPolicy +{ + private Func _timeoutProvider; + private TimeoutStrategy _timeoutStrategy; + private Action _onTimeout; - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => TimeoutEngine.Implementation( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeout); + internal TimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); } -} \ No newline at end of file + + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => TimeoutEngine.Implementation( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeout); +} diff --git a/src/Polly/Timeout/TimeoutRejectedException.cs b/src/Polly/Timeout/TimeoutRejectedException.cs index 81149e21340..6d899728263 100644 --- a/src/Polly/Timeout/TimeoutRejectedException.cs +++ b/src/Polly/Timeout/TimeoutRejectedException.cs @@ -3,49 +3,48 @@ using System.Runtime.Serialization; #endif -namespace Polly.Timeout +namespace Polly.Timeout; + +/// +/// Exception thrown when a delegate executed through a does not complete, before the configured timeout. +/// +#if NETSTANDARD2_0 +[Serializable] +#endif +public class TimeoutRejectedException : ExecutionRejectedException { /// - /// Exception thrown when a delegate executed through a does not complete, before the configured timeout. + /// Initializes a new instance of the class. /// -#if NETSTANDARD2_0 - [Serializable] -#endif - public class TimeoutRejectedException : ExecutionRejectedException + public TimeoutRejectedException() { - /// - /// Initializes a new instance of the class. - /// - public TimeoutRejectedException() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message. - public TimeoutRejectedException(string message) : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message. + public TimeoutRejectedException(string message) : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The inner exception. - public TimeoutRejectedException(string message, Exception innerException) : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The inner exception. + public TimeoutRejectedException(string message, Exception innerException) : base(message, innerException) + { + } #if NETSTANDARD2_0 - /// - /// Initializes a new instance of the class. - /// - /// The information. - /// The context. - protected TimeoutRejectedException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// + /// The information. + /// The context. + protected TimeoutRejectedException(SerializationInfo info, StreamingContext context) : base(info, context) + { } +#endif } diff --git a/src/Polly/Timeout/TimeoutStrategy.cs b/src/Polly/Timeout/TimeoutStrategy.cs index b948888fdc0..cb16a1fc859 100644 --- a/src/Polly/Timeout/TimeoutStrategy.cs +++ b/src/Polly/Timeout/TimeoutStrategy.cs @@ -1,19 +1,18 @@ using System.Threading; -namespace Polly.Timeout +namespace Polly.Timeout; + +/// +/// Defines strategies used by s to enforce timeouts. +/// +public enum TimeoutStrategy { /// - /// Defines strategies used by s to enforce timeouts. + /// An optimistic . The relies on a timing-out to cancel executed delegates by co-operative cancellation. + /// + Optimistic, + /// + /// An pessimistic . The will assume the delegates passed to be executed will not necessarily honor any timing-out , but the policy will still guarantee timing out (and returning to the caller) by other means. /// - public enum TimeoutStrategy - { - /// - /// An optimistic . The relies on a timing-out to cancel executed delegates by co-operative cancellation. - /// - Optimistic, - /// - /// An pessimistic . The will assume the delegates passed to be executed will not necessarily honor any timing-out , but the policy will still guarantee timing out (and returning to the caller) by other means. - /// - Pessimistic - } + Pessimistic } diff --git a/src/Polly/Timeout/TimeoutSyntax.cs b/src/Polly/Timeout/TimeoutSyntax.cs index 1b5b3b3c413..e66e157b6f4 100644 --- a/src/Polly/Timeout/TimeoutSyntax.cs +++ b/src/Polly/Timeout/TimeoutSyntax.cs @@ -2,383 +2,382 @@ using System; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static TimeoutPolicy Timeout(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// The policy instance. + /// seconds;Value must be greater than zero. + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) { - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static TimeoutPolicy Timeout(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// The policy instance. - /// seconds;Value must be greater than zero. - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return new TimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeout); - } + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return new TimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeout); } -} \ No newline at end of file +} diff --git a/src/Polly/Timeout/TimeoutTResultSyntax.cs b/src/Polly/Timeout/TimeoutTResultSyntax.cs index 94634fe0435..4c1724d6d02 100644 --- a/src/Polly/Timeout/TimeoutTResultSyntax.cs +++ b/src/Polly/Timeout/TimeoutTResultSyntax.cs @@ -2,390 +2,389 @@ using Polly.Timeout; using System.Threading.Tasks; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static TimeoutPolicy Timeout(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// The policy instance. + /// seconds;Value must be greater than zero. + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) { - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static TimeoutPolicy Timeout(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// The policy instance. - /// seconds;Value must be greater than zero. - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return new TimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeout); - } + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return new TimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeout); } -} \ No newline at end of file +} diff --git a/src/Polly/Timeout/TimeoutValidator.cs b/src/Polly/Timeout/TimeoutValidator.cs index 4175fee970c..3f6e96fdf71 100644 --- a/src/Polly/Timeout/TimeoutValidator.cs +++ b/src/Polly/Timeout/TimeoutValidator.cs @@ -1,20 +1,19 @@ using System; -namespace Polly.Timeout +namespace Polly.Timeout; + +internal static class TimeoutValidator { - internal static class TimeoutValidator + internal static void ValidateSecondsTimeout(int seconds) { - internal static void ValidateSecondsTimeout(int seconds) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - } - - internal static void ValidateTimeSpanTimeout(TimeSpan timeout) - { - if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) - throw new ArgumentOutOfRangeException(nameof(timeout), timeout, - $"{nameof(timeout)} must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout)"); - } + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + } + internal static void ValidateTimeSpanTimeout(TimeSpan timeout) + { + if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) + throw new ArgumentOutOfRangeException(nameof(timeout), timeout, + $"{nameof(timeout)} must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout)"); } + } diff --git a/src/Polly/Utilities/EmptyStruct.cs b/src/Polly/Utilities/EmptyStruct.cs index edc77510f76..8031bde0e91 100644 --- a/src/Polly/Utilities/EmptyStruct.cs +++ b/src/Polly/Utilities/EmptyStruct.cs @@ -1,10 +1,9 @@ -namespace Polly.Utilities +namespace Polly.Utilities; + +/// +/// A null struct for policies and actions which do not return a TResult. +/// +internal struct EmptyStruct { - /// - /// A null struct for policies and actions which do not return a TResult. - /// - internal struct EmptyStruct - { - internal static readonly EmptyStruct Instance = new EmptyStruct(); - } + internal static readonly EmptyStruct Instance = new EmptyStruct(); } diff --git a/src/Polly/Utilities/ExceptionExtensions.cs b/src/Polly/Utilities/ExceptionExtensions.cs index 037f9845087..d60cddd49d3 100644 --- a/src/Polly/Utilities/ExceptionExtensions.cs +++ b/src/Polly/Utilities/ExceptionExtensions.cs @@ -1,24 +1,23 @@ using System; using System.Runtime.ExceptionServices; -namespace Polly.Utilities +namespace Polly.Utilities; + +/// +/// Contains extension methods on the class. +/// +public static class ExceptionExtensions { /// - /// Contains extension methods on the class. + /// Rethrows the extended , , using the class to rethrow it with its original stack trace, if differs from . /// - public static class ExceptionExtensions + /// The exception to throw, if it differs from + /// The exception to compare against. + public static void RethrowWithOriginalStackTraceIfDiffersFrom(this Exception exceptionPossiblyToThrow, Exception exceptionToCompare) { - /// - /// Rethrows the extended , , using the class to rethrow it with its original stack trace, if differs from . - /// - /// The exception to throw, if it differs from - /// The exception to compare against. - public static void RethrowWithOriginalStackTraceIfDiffersFrom(this Exception exceptionPossiblyToThrow, Exception exceptionToCompare) + if (exceptionPossiblyToThrow != exceptionToCompare) { - if (exceptionPossiblyToThrow != exceptionToCompare) - { - ExceptionDispatchInfo.Capture(exceptionPossiblyToThrow).Throw(); - } + ExceptionDispatchInfo.Capture(exceptionPossiblyToThrow).Throw(); } } } diff --git a/src/Polly/Utilities/KeyHelper.cs b/src/Polly/Utilities/KeyHelper.cs index 1aff004fb76..d4c19bd3c7e 100644 --- a/src/Polly/Utilities/KeyHelper.cs +++ b/src/Polly/Utilities/KeyHelper.cs @@ -1,9 +1,8 @@ using System; -namespace Polly.Utilities +namespace Polly.Utilities; + +internal static class KeyHelper { - internal static class KeyHelper - { - public static string GuidPart() => Guid.NewGuid().ToString().Substring(0, 8); - } + public static string GuidPart() => Guid.NewGuid().ToString().Substring(0, 8); } diff --git a/src/Polly/Utilities/SystemClock.cs b/src/Polly/Utilities/SystemClock.cs index 1f971455165..51d6fc84ae0 100644 --- a/src/Polly/Utilities/SystemClock.cs +++ b/src/Polly/Utilities/SystemClock.cs @@ -2,65 +2,64 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Utilities +namespace Polly.Utilities; + +/// +/// Time related delegates used to support different compilation targets and to improve testability of the code. +/// +public static class SystemClock { /// - /// Time related delegates used to support different compilation targets and to improve testability of the code. + /// Allows the setting of a custom Thread.Sleep implementation for testing. + /// By default this will use the 's . /// - public static class SystemClock + public static Action Sleep = (timeSpan, cancellationToken) => { - /// - /// Allows the setting of a custom Thread.Sleep implementation for testing. - /// By default this will use the 's . - /// - public static Action Sleep = (timeSpan, cancellationToken) => - { - if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); - }; + if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); + }; - /// - /// Allows the setting of a custom async Sleep implementation for testing. - /// By default this will be a call to taking a - /// - public static Func SleepAsync = Task.Delay; + /// + /// Allows the setting of a custom async Sleep implementation for testing. + /// By default this will be a call to taking a + /// + public static Func SleepAsync = Task.Delay; - /// - /// Allows the setting of a custom DateTime.UtcNow implementation for testing. - /// By default this will be a call to - /// - public static Func UtcNow = () => DateTime.UtcNow; + /// + /// Allows the setting of a custom DateTime.UtcNow implementation for testing. + /// By default this will be a call to + /// + public static Func UtcNow = () => DateTime.UtcNow; - /// - /// Allows the setting of a custom DateTimeOffset.UtcNow implementation for testing. - /// By default this will be a call to - /// - public static Func DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; + /// + /// Allows the setting of a custom DateTimeOffset.UtcNow implementation for testing. + /// By default this will be a call to + /// + public static Func DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; - /// - /// Allows the setting of a custom method for cancelling tokens after a timespan, for use in testing. - /// By default this will be a call to CancellationTokenSource.CancelAfter(timespan) - /// - public static Action CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); + /// + /// Allows the setting of a custom method for cancelling tokens after a timespan, for use in testing. + /// By default this will be a call to CancellationTokenSource.CancelAfter(timespan) + /// + public static Action CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); - /// - /// Resets the custom implementations to their defaults. - /// Should be called during test teardowns. - /// - public static void Reset() + /// + /// Resets the custom implementations to their defaults. + /// Should be called during test teardowns. + /// + public static void Reset() + { + Sleep = (timeSpan, cancellationToken) => { - Sleep = (timeSpan, cancellationToken) => - { - if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); - }; + if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); + }; - SleepAsync = Task.Delay; + SleepAsync = Task.Delay; - UtcNow = () => DateTime.UtcNow; + UtcNow = () => DateTime.UtcNow; - DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; + DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; - CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); + CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); - } } -} \ No newline at end of file +} diff --git a/src/Polly/Utilities/TaskHelper.cs b/src/Polly/Utilities/TaskHelper.cs index be3cb06f8e4..82cb36491d9 100644 --- a/src/Polly/Utilities/TaskHelper.cs +++ b/src/Polly/Utilities/TaskHelper.cs @@ -1,21 +1,20 @@ using System.Threading.Tasks; -namespace Polly.Utilities +namespace Polly.Utilities; + +/// +/// Task helpers. +/// +public static class TaskHelper { /// - /// Task helpers. + /// Defines a completed Task for use as a completed, empty asynchronous delegate. /// - public static class TaskHelper - { - /// - /// Defines a completed Task for use as a completed, empty asynchronous delegate. - /// - public static Task EmptyTask = + public static Task EmptyTask = #if NETSTANDARD1_1 - Task.FromResult(true) + Task.FromResult(true) #else - Task.CompletedTask + Task.CompletedTask #endif - ; - } + ; } diff --git a/src/Polly/Utilities/TimedLock.cs b/src/Polly/Utilities/TimedLock.cs index 6c76efe7c18..8d01774a39a 100644 --- a/src/Polly/Utilities/TimedLock.cs +++ b/src/Polly/Utilities/TimedLock.cs @@ -1,95 +1,94 @@ using System; using System.Threading; -namespace Polly.Utilities -{ - // Adapted from the link below, with slight modifications. +namespace Polly.Utilities; - // http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking - // Ian Griffiths (original TimedLock author) wrote: - // Thanks to Eric Gunnerson for recommending this be a struct rather - // than a class - avoids a heap allocation. - // Thanks to Change Gillespie and Jocelyn Coulmance for pointing out - // the bugs that then crept in when I changed it to use struct... - // Thanks to John Sands for providing the necessary incentive to make - // me invent a way of using a struct in both release and debug builds - // without losing the debug leak tracking. - internal struct TimedLock : IDisposable - { - // The TimedLock class throws a LockTimeoutException if a lock cannot be obtained within the LockTimeout. This allows the easier discovery and debugging of deadlocks during Polly development, than if using a pure lock. - // We do not however ever want to throw a LockTimeoutException in production - hence the forked LockTimeout value below for DEBUG versus RELEASE builds. - // This applies particularly because CircuitBreakerPolicy runs state-change delegates during the lock, in order that the state change holds true (cannot be superseded by activity on other threads) while the delegate runs. +// Adapted from the link below, with slight modifications. + +// http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking +// Ian Griffiths (original TimedLock author) wrote: +// Thanks to Eric Gunnerson for recommending this be a struct rather +// than a class - avoids a heap allocation. +// Thanks to Change Gillespie and Jocelyn Coulmance for pointing out +// the bugs that then crept in when I changed it to use struct... +// Thanks to John Sands for providing the necessary incentive to make +// me invent a way of using a struct in both release and debug builds +// without losing the debug leak tracking. +internal struct TimedLock : IDisposable +{ + // The TimedLock class throws a LockTimeoutException if a lock cannot be obtained within the LockTimeout. This allows the easier discovery and debugging of deadlocks during Polly development, than if using a pure lock. + // We do not however ever want to throw a LockTimeoutException in production - hence the forked LockTimeout value below for DEBUG versus RELEASE builds. + // This applies particularly because CircuitBreakerPolicy runs state-change delegates during the lock, in order that the state change holds true (cannot be superseded by activity on other threads) while the delegate runs. #if DEBUG - private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(5); #else - private static readonly TimeSpan LockTimeout = TimeSpan.FromMilliseconds(int.MaxValue); + private static readonly TimeSpan LockTimeout = TimeSpan.FromMilliseconds(int.MaxValue); #endif - public static TimedLock Lock(object o) - { - return Lock(o, LockTimeout); - } + public static TimedLock Lock(object o) + { + return Lock(o, LockTimeout); + } - private static TimedLock Lock(object o, TimeSpan timeout) + private static TimedLock Lock(object o, TimeSpan timeout) + { + TimedLock tl = new TimedLock(o); + if (!Monitor.TryEnter(o, timeout)) { - TimedLock tl = new TimedLock(o); - if (!Monitor.TryEnter(o, timeout)) - { #if DEBUG - GC.SuppressFinalize(tl.leakDetector); + GC.SuppressFinalize(tl.leakDetector); #endif - throw new LockTimeoutException(); - } - - return tl; + throw new LockTimeoutException(); } - private TimedLock(object o) - { - target = o; + return tl; + } + + private TimedLock(object o) + { + target = o; #if DEBUG - leakDetector = new Sentinel(); + leakDetector = new Sentinel(); #endif - } - private object target; + } + private object target; - public void Dispose() - { - Monitor.Exit(target); + public void Dispose() + { + Monitor.Exit(target); - // It's a bad error if someone forgets to call Dispose, - // so in Debug builds, we put a finalizer in to detect - // the error. If Dispose is called, we suppress the - // finalizer. + // It's a bad error if someone forgets to call Dispose, + // so in Debug builds, we put a finalizer in to detect + // the error. If Dispose is called, we suppress the + // finalizer. #if DEBUG - GC.SuppressFinalize(leakDetector); + GC.SuppressFinalize(leakDetector); #endif - } + } #if DEBUG - // (In Debug mode, we make it a class so that we can add a finalizer - // in order to detect when the object is not freed.) - private class Sentinel + // (In Debug mode, we make it a class so that we can add a finalizer + // in order to detect when the object is not freed.) + private class Sentinel + { + ~Sentinel() { - ~Sentinel() - { - // If this finalizer runs, someone somewhere failed to - // call Dispose, which means we've failed to leave - // a monitor! + // If this finalizer runs, someone somewhere failed to + // call Dispose, which means we've failed to leave + // a monitor! #if NETSTANDARD2_0 - System.Diagnostics.Debug.Fail("Undisposed lock"); + System.Diagnostics.Debug.Fail("Undisposed lock"); #endif - } } - private Sentinel leakDetector; + } + private Sentinel leakDetector; #endif - } +} - internal class LockTimeoutException : Exception +internal class LockTimeoutException : Exception +{ + public LockTimeoutException() : base("Timeout waiting for lock") { - public LockTimeoutException() : base("Timeout waiting for lock") - { - } } -} \ No newline at end of file +} diff --git a/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs b/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs index c37eafde1aa..4dc57e16620 100644 --- a/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs +++ b/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs @@ -1,40 +1,39 @@ -namespace Polly.Wrap +namespace Polly.Wrap; + +public partial class AsyncPolicyWrap { - public partial class AsyncPolicyWrap + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) { - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) - { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); - } + base.SetPolicyContext(executionContext, out _, out _); } +} - public partial class AsyncPolicyWrap +public partial class AsyncPolicyWrap +{ + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) { - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) - { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); - } + base.SetPolicyContext(executionContext, out _, out _); } } diff --git a/src/Polly/Wrap/AsyncPolicyWrap.cs b/src/Polly/Wrap/AsyncPolicyWrap.cs index e86b432cfa6..af3e77e5117 100644 --- a/src/Polly/Wrap/AsyncPolicyWrap.cs +++ b/src/Polly/Wrap/AsyncPolicyWrap.cs @@ -3,176 +3,175 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Wrap +namespace Polly.Wrap; + +/// +/// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. +/// +public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap { + private IAsyncPolicy _outer; + private IAsyncPolicy _inner; + /// - /// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. + /// Returns the outer in this /// - public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap - { - private IAsyncPolicy _outer; - private IAsyncPolicy _inner; + public IsPolicy Outer => _outer; - /// - /// Returns the outer in this - /// - public IsPolicy Outer => _outer; + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => _inner; - /// - /// Returns the next inner in this - /// - public IsPolicy Inner => _inner; + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates) + { + _outer = outer; + _inner = inner; + } - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates) - { - _outer = outer; - _inner = inner; - } + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outer, + _inner + ); + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outer, + _inner + ); +} - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outer, - _inner - ); - - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outer, - _inner - ); - } +/// +/// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. +/// +/// The return type of delegates which may be executed through the policy. +public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap +{ + private IAsyncPolicy _outerNonGeneric; + private IAsyncPolicy _innerNonGeneric; + + private IAsyncPolicy _outerGeneric; + private IAsyncPolicy _innerGeneric; /// - /// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. + /// Returns the outer in this /// - /// The return type of delegates which may be executed through the policy. - public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap - { - private IAsyncPolicy _outerNonGeneric; - private IAsyncPolicy _innerNonGeneric; + public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; - private IAsyncPolicy _outerGeneric; - private IAsyncPolicy _innerGeneric; + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; - /// - /// Returns the outer in this - /// - public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates, ResultPredicates.None) + { + _outerNonGeneric = outer; + _innerGeneric = inner; + } - /// - /// Returns the next inner in this - /// - public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) + { + _outerGeneric = outer; + _innerNonGeneric = inner; + } - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates, ResultPredicates.None) - { - _outerNonGeneric = outer; - _innerGeneric = inner; - } + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) + { + _outerGeneric = outer; + _innerGeneric = inner; + } - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) + /// + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + if (_outerNonGeneric != null) { - _outerGeneric = outer; - _innerNonGeneric = inner; - } + if (_innerNonGeneric != null) + { + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerNonGeneric, + _innerNonGeneric + ); + } + else if (_innerGeneric != null) + { + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerNonGeneric, + _innerGeneric + ); - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) - { - _outerGeneric = outer; - _innerGeneric = inner; + } + else + { + throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); + } } - - /// - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) + else if (_outerGeneric != null) { - if (_outerNonGeneric != null) + if (_innerNonGeneric != null) { - if (_innerNonGeneric != null) - { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerNonGeneric, - _innerNonGeneric - ); - } - else if (_innerGeneric != null) - { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerNonGeneric, - _innerGeneric - ); - - } - else - { - throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); - } + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerGeneric, + _innerNonGeneric + ); + } - else if (_outerGeneric != null) + else if (_innerGeneric != null) { - if (_innerNonGeneric != null) - { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerGeneric, - _innerNonGeneric - ); - - } - else if (_innerGeneric != null) - { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerGeneric, - _innerGeneric - ); - - } - else - { - throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); - } + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerGeneric, + _innerGeneric + ); + } else { - throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an outer policy."); + throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); } } + else + { + throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an outer policy."); + } } } diff --git a/src/Polly/Wrap/AsyncPolicyWrapEngine.cs b/src/Polly/Wrap/AsyncPolicyWrapEngine.cs index 18b33157062..5c966ed9541 100644 --- a/src/Polly/Wrap/AsyncPolicyWrapEngine.cs +++ b/src/Polly/Wrap/AsyncPolicyWrapEngine.cs @@ -2,104 +2,103 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Wrap +namespace Polly.Wrap; + +internal static class AsyncPolicyWrapEngine { - internal static class AsyncPolicyWrapEngine - { - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); - - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, - continueOnCapturedContext ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, - continueOnCapturedContext + context, + cancellationToken, + continueOnCapturedContext ).ConfigureAwait(continueOnCapturedContext); - internal static async Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - action, - ctx, - ct, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); + + internal static async Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + action, + ctx, + ct, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); - } } diff --git a/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs b/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs index cf87e61fa0f..fc013078b70 100644 --- a/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs +++ b/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs @@ -2,172 +2,171 @@ using System.Linq; using Polly.Wrap; -namespace Polly +namespace Polly; + +public partial class AsyncPolicy { - public partial class AsyncPolicy + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) { - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); - } + return new AsyncPolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// The return type of delegates which may be executed through the policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// The return type of delegates which may be executed through the policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); - } + return new AsyncPolicyWrap( + this, + innerPolicy + ); } +} - public partial class AsyncPolicy +public partial class AsyncPolicy +{ + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) { - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); - } + return new AsyncPolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); - } + return new AsyncPolicyWrap( + this, + innerPolicy + ); } +} - public partial class Policy +public partial class Policy +{ + /// + /// Creates a of the given policies. + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) { - /// - /// Creates a of the given policies. - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) + switch (policies.Length) { - switch (policies.Length) - { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); - - default: - return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); - } + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); + + default: + return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); } + } - /// - /// Creates a of the given policies governing delegates returning values of type . - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The return type of delegates which may be executed through the policy. - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) + /// + /// Creates a of the given policies governing delegates returning values of type . + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The return type of delegates which may be executed through the policy. + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) + { + switch (policies.Length) { - switch (policies.Length) - { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); - - default: - return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); - } + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); + + default: + return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); } } +} +/// +/// Defines extensions for configuring instances on an or . +/// +public static class IAsyncPolicyPolicyWrapExtensions +{ /// - /// Defines extensions for configuring instances on an or . + /// Wraps the specified outer policy round the inner policy. /// - public static class IAsyncPolicyPolicyWrapExtensions + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) { - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); - } + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); } } diff --git a/src/Polly/Wrap/IPolicyWrap.cs b/src/Polly/Wrap/IPolicyWrap.cs index 075f0060cdb..be593f26cc1 100644 --- a/src/Polly/Wrap/IPolicyWrap.cs +++ b/src/Polly/Wrap/IPolicyWrap.cs @@ -1,26 +1,25 @@ -namespace Polly.Wrap +namespace Polly.Wrap; + +/// +/// Defines properties and methods common to all PolicyWrap policies. +/// +public interface IPolicyWrap : IsPolicy { /// - /// Defines properties and methods common to all PolicyWrap policies. + /// Returns the outer in this /// - public interface IPolicyWrap : IsPolicy - { - /// - /// Returns the outer in this - /// - IsPolicy Outer { get; } - - /// - /// Returns the next inner in this - /// - IsPolicy Inner { get; } - } + IsPolicy Outer { get; } /// - /// Defines properties and methods common to all PolicyWrap policies generic-typed for executions returning results of type . + /// Returns the next inner in this /// - public interface IPolicyWrap : IPolicyWrap - { + IsPolicy Inner { get; } +} + +/// +/// Defines properties and methods common to all PolicyWrap policies generic-typed for executions returning results of type . +/// +public interface IPolicyWrap : IPolicyWrap +{ - } } diff --git a/src/Polly/Wrap/IPolicyWrapExtension.cs b/src/Polly/Wrap/IPolicyWrapExtension.cs index 56a6d033ca9..3348254894f 100644 --- a/src/Polly/Wrap/IPolicyWrapExtension.cs +++ b/src/Polly/Wrap/IPolicyWrapExtension.cs @@ -2,81 +2,80 @@ using System.Collections.Generic; using System.Linq; -namespace Polly.Wrap +namespace Polly.Wrap; + +/// +/// Extension methods for IPolicyWrap. +/// +public static class IPolicyWrapExtension { /// - /// Extension methods for IPolicyWrap. + /// Returns all the policies in this , in Outer-to-Inner order. /// - public static class IPolicyWrapExtension + /// The for which to return policies. + /// An of all the policies in the wrap. + public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) { - /// - /// Returns all the policies in this , in Outer-to-Inner order. - /// - /// The for which to return policies. - /// An of all the policies in the wrap. - public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) + var childPolicies = new[] { policyWrap.Outer, policyWrap.Inner }; + foreach (var childPolicy in childPolicies) { - var childPolicies = new[] { policyWrap.Outer, policyWrap.Inner }; - foreach (var childPolicy in childPolicies) + if (childPolicy is IPolicyWrap anotherWrap) { - if (childPolicy is IPolicyWrap anotherWrap) - { - foreach (var policy in anotherWrap.GetPolicies()) - { - yield return policy; - } - } - else if (childPolicy != null) + foreach (var policy in anotherWrap.GetPolicies()) { - yield return childPolicy; + yield return policy; } } + else if (childPolicy != null) + { + yield return childPolicy; + } } + } - /// - /// Returns all the policies in this of type , in Outer-to-Inner order. - /// - /// The for which to return policies. - /// The type of policies to return. - /// An of all the policies of the given type. - public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) - => policyWrap.GetPolicies().OfType(); + /// + /// Returns all the policies in this of type , in Outer-to-Inner order. + /// + /// The for which to return policies. + /// The type of policies to return. + /// An of all the policies of the given type. + public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) + => policyWrap.GetPolicies().OfType(); - /// - /// Returns all the policies in this of type matching the filter, in Outer-to-Inner order. - /// - /// The for which to return policies. - /// A filter to apply to any policies of type found. - /// The type of policies to return. - /// An of all the policies of the given type, matching the filter. - public static IEnumerable GetPolicies(this IPolicyWrap policyWrap, Func filter) - { - if (filter == null) throw new ArgumentNullException(nameof(filter)); - return policyWrap.GetPolicies().OfType().Where(filter); - } + /// + /// Returns all the policies in this of type matching the filter, in Outer-to-Inner order. + /// + /// The for which to return policies. + /// A filter to apply to any policies of type found. + /// The type of policies to return. + /// An of all the policies of the given type, matching the filter. + public static IEnumerable GetPolicies(this IPolicyWrap policyWrap, Func filter) + { + if (filter == null) throw new ArgumentNullException(nameof(filter)); + return policyWrap.GetPolicies().OfType().Where(filter); + } - /// - /// Returns the single policy in this of type . - /// - /// The for which to search for the policy. - /// The type of policy to return. - /// A if one is found; else null. - /// InvalidOperationException, if more than one policy of the type is found in the wrap. - public static TPolicy GetPolicy(this IPolicyWrap policyWrap) - => policyWrap.GetPolicies().OfType().SingleOrDefault(); + /// + /// Returns the single policy in this of type . + /// + /// The for which to search for the policy. + /// The type of policy to return. + /// A if one is found; else null. + /// InvalidOperationException, if more than one policy of the type is found in the wrap. + public static TPolicy GetPolicy(this IPolicyWrap policyWrap) + => policyWrap.GetPolicies().OfType().SingleOrDefault(); - /// - /// Returns the single policy in this of type matching the filter. - /// - /// The for which to search for the policy. - /// A filter to apply to any policies of type found. - /// The type of policy to return. - /// A matching if one is found; else null. - /// InvalidOperationException, if more than one policy of the type is found in the wrap. - public static TPolicy GetPolicy(this IPolicyWrap policyWrap, Func filter) - { - if (filter == null) throw new ArgumentNullException(nameof(filter)); - return policyWrap.GetPolicies().OfType().SingleOrDefault(filter); - } + /// + /// Returns the single policy in this of type matching the filter. + /// + /// The for which to search for the policy. + /// A filter to apply to any policies of type found. + /// The type of policy to return. + /// A matching if one is found; else null. + /// InvalidOperationException, if more than one policy of the type is found in the wrap. + public static TPolicy GetPolicy(this IPolicyWrap policyWrap, Func filter) + { + if (filter == null) throw new ArgumentNullException(nameof(filter)); + return policyWrap.GetPolicies().OfType().SingleOrDefault(filter); } } diff --git a/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs b/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs index 438c0b9e817..6ed12582402 100644 --- a/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs +++ b/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs @@ -1,40 +1,39 @@ -namespace Polly.Wrap +namespace Polly.Wrap; + +public partial class PolicyWrap { - public partial class PolicyWrap + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) { - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) - { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); - } + base.SetPolicyContext(executionContext, out _, out _); } +} - public partial class PolicyWrap +public partial class PolicyWrap +{ + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) { - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) - { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); - } + base.SetPolicyContext(executionContext, out _, out _); } } diff --git a/src/Polly/Wrap/PolicyWrap.cs b/src/Polly/Wrap/PolicyWrap.cs index 712dc775784..aef1864cfb0 100644 --- a/src/Polly/Wrap/PolicyWrap.cs +++ b/src/Polly/Wrap/PolicyWrap.cs @@ -2,163 +2,162 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Wrap +namespace Polly.Wrap; + +/// +/// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. +/// +public partial class PolicyWrap : Policy, IPolicyWrap { + private ISyncPolicy _outer; + private ISyncPolicy _inner; + /// - /// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. + /// Returns the outer in this /// - public partial class PolicyWrap : Policy, IPolicyWrap - { - private ISyncPolicy _outer; - private ISyncPolicy _inner; + public IsPolicy Outer => _outer; - /// - /// Returns the outer in this - /// - public IsPolicy Outer => _outer; + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => _inner; - /// - /// Returns the next inner in this - /// - public IsPolicy Inner => _inner; + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates) + { + _outer = outer; + _inner = inner; + } - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates) - { - _outer = outer; - _inner = inner; - } + /// + [DebuggerStepThrough] + protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) + => PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outer, + _inner + ); + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outer, + _inner + ); +} - /// - [DebuggerStepThrough] - protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) - => PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outer, - _inner - ); - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outer, - _inner - ); - } +/// +/// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. +/// +/// The return type of delegates which may be executed through the policy. +public partial class PolicyWrap : Policy, IPolicyWrap +{ + private ISyncPolicy _outerNonGeneric; + private ISyncPolicy _innerNonGeneric; + + private ISyncPolicy _outerGeneric; + private ISyncPolicy _innerGeneric; /// - /// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. + /// Returns the outer in this /// - /// The return type of delegates which may be executed through the policy. - public partial class PolicyWrap : Policy, IPolicyWrap - { - private ISyncPolicy _outerNonGeneric; - private ISyncPolicy _innerNonGeneric; + public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; - private ISyncPolicy _outerGeneric; - private ISyncPolicy _innerGeneric; + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; - /// - /// Returns the outer in this - /// - public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates, ResultPredicates.None) + { + _outerNonGeneric = outer; + _innerGeneric = inner; + } - /// - /// Returns the next inner in this - /// - public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) + { + _outerGeneric = outer; + _innerNonGeneric = inner; + } - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates, ResultPredicates.None) - { - _outerNonGeneric = outer; - _innerGeneric = inner; - } + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) + { + _outerGeneric = outer; + _innerGeneric = inner; + } - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + if (_outerNonGeneric != null) { - _outerGeneric = outer; - _innerNonGeneric = inner; - } + if (_innerNonGeneric != null) + { + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerNonGeneric, + _innerNonGeneric + ); + } + else if (_innerGeneric != null) + { + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerNonGeneric, + _innerGeneric + ); - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) - { - _outerGeneric = outer; - _innerGeneric = inner; + } + else + { + throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); + } } - - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + else if (_outerGeneric != null) { - if (_outerNonGeneric != null) + if (_innerNonGeneric != null) { - if (_innerNonGeneric != null) - { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerNonGeneric, - _innerNonGeneric - ); - } - else if (_innerGeneric != null) - { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerNonGeneric, - _innerGeneric - ); - - } - else - { - throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); - } + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerGeneric, + _innerNonGeneric + ); + } - else if (_outerGeneric != null) + else if (_innerGeneric != null) { - if (_innerNonGeneric != null) - { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerGeneric, - _innerNonGeneric - ); - - } - else if (_innerGeneric != null) - { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerGeneric, - _innerGeneric - ); - - } - else - { - throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); - } + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerGeneric, + _innerGeneric + ); + } else { - throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an outer policy."); + throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); } } + else + { + throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an outer policy."); + } } } diff --git a/src/Polly/Wrap/PolicyWrapEngine.cs b/src/Polly/Wrap/PolicyWrapEngine.cs index 88cca6ab465..a0809615535 100644 --- a/src/Polly/Wrap/PolicyWrapEngine.cs +++ b/src/Polly/Wrap/PolicyWrapEngine.cs @@ -1,48 +1,47 @@ using System; using System.Threading; -namespace Polly.Wrap +namespace Polly.Wrap; + +internal static class PolicyWrapEngine { - internal static class PolicyWrapEngine - { - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static void Implementation( - Action action, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(action, ctx, ct), context, cancellationToken); - } + internal static void Implementation( + Action action, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(action, ctx, ct), context, cancellationToken); } diff --git a/src/Polly/Wrap/PolicyWrapSyntax.cs b/src/Polly/Wrap/PolicyWrapSyntax.cs index f2ebb403ebf..0a057f6d82b 100644 --- a/src/Polly/Wrap/PolicyWrapSyntax.cs +++ b/src/Polly/Wrap/PolicyWrapSyntax.cs @@ -2,172 +2,171 @@ using System.Linq; using Polly.Wrap; -namespace Polly +namespace Polly; + +public partial class Policy { - public partial class Policy + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) { - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); - } + return new PolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// The return type of delegates which may be executed through the policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// The return type of delegates which may be executed through the policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); - } + return new PolicyWrap( + this, + innerPolicy + ); } +} - public partial class Policy +public partial class Policy +{ + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) { - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); - } + return new PolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); - } + return new PolicyWrap( + this, + innerPolicy + ); } +} - public partial class Policy +public partial class Policy +{ + /// + /// Creates a of the given policies. + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static PolicyWrap Wrap(params ISyncPolicy[] policies) { - /// - /// Creates a of the given policies. - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static PolicyWrap Wrap(params ISyncPolicy[] policies) + switch (policies.Length) { - switch (policies.Length) - { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new PolicyWrap((Policy)policies[0], policies[1]); - - default: - return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); - } + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new PolicyWrap((Policy)policies[0], policies[1]); + + default: + return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); } + } - /// - /// Creates a of the given policies governing delegates returning values of type . - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The return type of delegates which may be executed through the policy. - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static PolicyWrap Wrap(params ISyncPolicy[] policies) + /// + /// Creates a of the given policies governing delegates returning values of type . + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The return type of delegates which may be executed through the policy. + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static PolicyWrap Wrap(params ISyncPolicy[] policies) + { + switch (policies.Length) { - switch (policies.Length) - { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new PolicyWrap((Policy)policies[0], policies[1]); - - default: - return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); - } + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new PolicyWrap((Policy)policies[0], policies[1]); + + default: + return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); } } +} +/// +/// Defines extensions for configuring instances on an or . +/// +public static class ISyncPolicyPolicyWrapExtensions +{ /// - /// Defines extensions for configuring instances on an or . + /// Wraps the specified outer policy round the inner policy. /// - public static class ISyncPolicyPolicyWrapExtensions + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) { - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy) outerPolicy).Wrap(innerPolicy); - } + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy) outerPolicy).Wrap(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy)outerPolicy).Wrap(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy)outerPolicy).Wrap(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy)outerPolicy).Wrap(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy)outerPolicy).Wrap(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy)outerPolicy).Wrap(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy)outerPolicy).Wrap(innerPolicy); } } From 053d891d07b14544fbca58e67feb3b0b75ba838b Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 14:07:13 +0000 Subject: [PATCH 17/24] Create .git-blame-ignore-revs Ignore commits for file-scoped namespaces in `git blame` on GitHub.com. --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..c25b162ab40 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,3 @@ +# Use file-scoped namespaces +a450cb69b5e4549f5515cdb057a68771f56cefd7 +e32730600746b479db0241826b9bdef8eec9bb28 From 80002fe20cd8ab8ba9e159e5adbd8d827bb8e574 Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 14:52:12 +0000 Subject: [PATCH 18/24] Revert "expression body members" This reverts commit a6f49c7afc6ceb3d5d08c3d8c32710539f71b61c. --- src/Polly.Benchmarks/Cache.cs | 6 ++-- src/Polly.Benchmarks/Workloads.cs | 8 +++-- .../Bulkhead/BulkheadAsyncSpecs.cs | 12 ++++--- src/Polly.Specs/Bulkhead/BulkheadSpecs.cs | 12 ++++--- .../Bulkhead/BulkheadTResultAsyncSpecs.cs | 13 +++++--- .../Bulkhead/BulkheadTResultSpecs.cs | 12 ++++--- .../Helpers/Bulkhead/TraceableAction.cs | 2 +- .../Helpers/Caching/StubCacheKeyStrategy.cs | 10 ++++-- .../Helpers/Caching/StubCacheProvider.cs | 12 ++++--- .../Caching/StubErroringCacheProvider.cs | 6 ++-- .../ContextualPolicyExtensionsAsync.cs | 7 ++-- .../ContextualPolicyTResultExtensions.cs | 12 ++++--- .../ContextualPolicyTResultExtensionsAsync.cs | 12 ++++--- .../AddBehaviourIfHandlePolicy.cs | 6 ++-- .../AsyncAddBehaviourIfHandlePolicy.cs | 11 +++++-- .../PreExecute/AsyncPreExecutePolicy.cs | 32 +++++++++++++------ .../Custom/PreExecute/PreExecutePolicy.cs | 32 +++++++++++++------ .../Helpers/PolicyExtensionsAsync.cs | 6 ++-- .../Helpers/PolicyTResultExtensions.cs | 18 +++++++---- .../Helpers/PolicyTResultExtensionsAsync.cs | 30 +++++++++++------ .../RateLimit/AsyncRateLimitPolicySpecs.cs | 14 +++++--- .../AsyncRateLimitPolicyTResultSpecs.cs | 22 +++++++++---- .../RateLimit/RateLimitPolicySpecs.cs | 12 ++++--- .../RateLimit/RateLimitPolicyTResultSpecs.cs | 18 +++++++---- .../Registry/ReadOnlyPolicyRegistrySpecs.cs | 6 ++-- src/Polly/Bulkhead/AsyncBulkheadPolicy.cs | 12 ++++--- src/Polly/Caching/AsyncCachePolicy.cs | 18 +++++++---- src/Polly/Caching/CachePolicy.cs | 6 ++-- .../CircuitBreaker/BrokenCircuitException.cs | 2 +- .../CircuitBreaker/CircuitStateController.cs | 15 ++++++--- src/Polly/CircuitBreaker/HealthCount.cs | 2 +- src/Polly/PolicyBuilder.cs | 24 +++++++++----- src/Polly/RateLimit/AsyncRateLimitSyntax.cs | 6 ++-- .../RateLimit/AsyncRateLimitTResultSyntax.cs | 18 +++++++---- src/Polly/RateLimit/RateLimitSyntax.cs | 6 ++-- src/Polly/RateLimit/RateLimitTResultSyntax.cs | 18 +++++++---- src/Polly/Timeout/AsyncTimeoutPolicy.cs | 6 ++-- src/Polly/Utilities/TimedLock.cs | 6 ++-- 38 files changed, 316 insertions(+), 154 deletions(-) diff --git a/src/Polly.Benchmarks/Cache.cs b/src/Polly.Benchmarks/Cache.cs index 188c3d55296..58d3d37f5ba 100644 --- a/src/Polly.Benchmarks/Cache.cs +++ b/src/Polly.Benchmarks/Cache.cs @@ -96,8 +96,10 @@ public void Put(string key, object value, Ttl ttl) _cache.Set(key, value, options); } - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) => - Task.FromResult(TryGet(key)); + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) { diff --git a/src/Polly.Benchmarks/Workloads.cs b/src/Polly.Benchmarks/Workloads.cs index 837d25f4937..fab7ecc3e1c 100644 --- a/src/Polly.Benchmarks/Workloads.cs +++ b/src/Polly.Benchmarks/Workloads.cs @@ -37,10 +37,14 @@ internal static async Task ActionInfiniteAsync(CancellationToken cancellationTok internal static Task FuncAsync(CancellationToken cancellationToken) => Task.FromResult(default); internal static TResult FuncThrows() - where TException : Exception, new() => + where TException : Exception, new() + { throw new TException(); + } internal static Task FuncThrowsAsync() - where TException : Exception, new() => + where TException : Exception, new() + { throw new TException(); + } } \ No newline at end of file diff --git a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs index 6b60386d662..45879016796 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs @@ -85,11 +85,15 @@ public void Should_call_onBulkheadRejected_with_passed_context() #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) => - Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) => - action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + } #endregion diff --git a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs index 47d10dc0e7e..ae955c341cf 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs @@ -82,11 +82,15 @@ public void Should_call_onBulkheadRejected_with_passed_context() #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) => - Policy.Bulkhead(maxParallelization, maxQueuingActions); + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.Bulkhead(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) => - action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); + } #endregion diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs index 8b299766b8c..641ad4a7911 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs @@ -85,6 +85,7 @@ public void Should_call_onBulkheadRejected_with_passed_context() contextPassedToOnRejected.Should().NotBeNull(); contextPassedToOnRejected.OperationKey.Should().Be(operationKey); contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + } } @@ -93,11 +94,15 @@ public void Should_call_onBulkheadRejected_with_passed_context() #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) => - Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) => - action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + } #endregion diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs index 374bfa81bf4..fa2ce2d6527 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs @@ -92,11 +92,15 @@ public void Should_call_onBulkheadRejected_with_passed_context() #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) => - Policy.Bulkhead(maxParallelization, maxQueuingActions); + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.Bulkhead(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) => - action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); + } #endregion } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs b/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs index 6207303c25f..6b30bb2caba 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs @@ -23,7 +23,7 @@ public class TraceableAction : IDisposable public TraceableActionStatus Status { - get => _status; + get { return _status; } set { _status = value; diff --git a/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs b/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs index 03e90163df8..7bb2ec9e8b4 100644 --- a/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs +++ b/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs @@ -10,9 +10,13 @@ internal class StubCacheKeyStrategy : ICacheKeyStrategy { private readonly Func strategy; - public StubCacheKeyStrategy(Func strategy) => + public StubCacheKeyStrategy(Func strategy) + { this.strategy = strategy; + } - public string GetCacheKey(Context context) => - strategy(context); + public string GetCacheKey(Context context) + { + return strategy(context); + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs b/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs index 38c7bf5732f..e9b577f3363 100644 --- a/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs +++ b/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs @@ -42,14 +42,18 @@ public CacheItem(object value, Ttl ttl) return (false, null); } - public void Put(string key, object value, Ttl ttl) => - cachedValues[key] = new(value, ttl); + public void Put(string key, object value, Ttl ttl) + { + cachedValues[key] = new CacheItem(value, ttl); + } #region Naive async-over-sync implementation // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) => - Task.FromResult(TryGet(key)); + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) { diff --git a/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs b/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs index 1a9da563a54..2af6aade13f 100644 --- a/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs +++ b/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs @@ -34,8 +34,10 @@ public void Put(string key, object value, Ttl ttl) #region Naive async-over-sync implementation // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) => - Task.FromResult(TryGet(key)); + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) { diff --git a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs index 506f0281a4d..020e4dfc7cf 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs @@ -29,6 +29,9 @@ public static class ContextualPolicyExtensionsAsync }, contextData, cancellationToken); } - public static Task RaiseExceptionAsync(this AsyncPolicy policy, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() => - policy.RaiseExceptionAsync(1, contextData, configureException, cancellationToken); + public static Task RaiseExceptionAsync(this AsyncPolicy policy, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() + { + return policy.RaiseExceptionAsync(1, contextData, configureException, cancellationToken); + } + } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs index 9b45ef35177..219bbcc64b7 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs @@ -8,8 +8,10 @@ public static class ContextualPolicyTResultExtensions { public static TResult RaiseResultSequence(this Policy policy, IDictionary contextData, - params TResult[] resultsToRaise) => - policy.RaiseResultSequence(contextData, resultsToRaise.ToList()); + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequence(contextData, resultsToRaise.ToList()); + } public static TResult RaiseResultSequence(this Policy policy, IDictionary contextData, @@ -30,8 +32,10 @@ public static TResult RaiseResultSequence(this Policy policy, public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, IDictionary contextData, - params TResult[] resultsToRaise) => - policy.RaiseResultSequenceOnExecuteAndCapture(contextData, resultsToRaise.ToList()); + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceOnExecuteAndCapture(contextData, resultsToRaise.ToList()); + } public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, IDictionary contextData, diff --git a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs index 627a90ccbc7..0e4caeccdbd 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs @@ -11,8 +11,10 @@ public static class ContextualPolicyTResultExtensionsAsync public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IDictionary contextData, - params TResult[] resultsToRaise) => - policy.RaiseResultSequenceAsync(contextData, CancellationToken.None, resultsToRaise.ToList()); + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAsync(contextData, CancellationToken.None, resultsToRaise.ToList()); + } public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IDictionary contextData, CancellationToken cancellationToken, IEnumerable resultsToRaise) { @@ -29,8 +31,10 @@ public static Task RaiseResultSequenceAsync(this AsyncPolicy> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, params TResult[] resultsToRaise) => - policy.RaiseResultSequenceOnExecuteAndCaptureAsync(contextData, resultsToRaise.ToList()); + public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceOnExecuteAndCaptureAsync(contextData, resultsToRaise.ToList()); + } public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, IEnumerable resultsToRaise) { diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs index 609fb1bc0ae..417d4f7f74e 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs @@ -41,8 +41,9 @@ internal AddBehaviourIfHandlePolicy( _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); } - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) => - AddBehaviourIfHandleEngine.Implementation( + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return AddBehaviourIfHandleEngine.Implementation( ExceptionPredicates, ResultPredicates, _behaviourIfHandle, @@ -50,4 +51,5 @@ protected override TResult Implementation(Func : AsyncPolicy internal AsyncAddBehaviourIfHandlePolicy( Func, Task> behaviourIfHandle, PolicyBuilder policyBuilder) - : base(policyBuilder) => + : base(policyBuilder) + { _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + } + protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncAddBehaviourIfHandleEngine.ImplementationAsync( + bool continueOnCapturedContext) + { + return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( ExceptionPredicates, ResultPredicates, _behaviourIfHandle, @@ -51,4 +55,5 @@ protected override Task ImplementationAsync(Func _preExecute; - public static AsyncPreExecutePolicy CreateAsync(Func preExecute) => - new(preExecute); + public static AsyncPreExecutePolicy CreateAsync(Func preExecute) + { + return new AsyncPreExecutePolicy(preExecute); + } - internal AsyncPreExecutePolicy(Func preExecute) => + internal AsyncPreExecutePolicy(Func preExecute) + { _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + bool continueOnCapturedContext) + { + return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + } } internal class AsyncPreExecutePolicy : AsyncPolicy { private Func _preExecute; - public static AsyncPreExecutePolicy CreateAsync(Func preExecute) => - new AsyncPreExecutePolicy(preExecute); + public static AsyncPreExecutePolicy CreateAsync(Func preExecute) + { + return new AsyncPreExecutePolicy(preExecute); + } - internal AsyncPreExecutePolicy(Func preExecute) => + internal AsyncPreExecutePolicy(Func preExecute) + { _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + bool continueOnCapturedContext) + { + return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs index abccf2aaa48..0c6de5dbcec 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs @@ -7,26 +7,38 @@ internal class PreExecutePolicy : Policy { private Action _preExecute; - public static PreExecutePolicy Create(Action preExecute) => - new(preExecute); + public static PreExecutePolicy Create(Action preExecute) + { + return new PreExecutePolicy(preExecute); + } - internal PreExecutePolicy(Action preExecute) => + internal PreExecutePolicy(Action preExecute) + { _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) => - PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + } } internal class PreExecutePolicy : Policy { private Action _preExecute; - public static PreExecutePolicy Create(Action preExecute) => - new PreExecutePolicy(preExecute); + public static PreExecutePolicy Create(Action preExecute) + { + return new PreExecutePolicy(preExecute); + } - internal PreExecutePolicy(Action preExecute) => + internal PreExecutePolicy(Action preExecute) + { _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) => - PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs index e851a963724..b339599ae4b 100644 --- a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs @@ -28,8 +28,10 @@ public static Task RaiseExceptionAsync(this AsyncPolicy policy, TExc return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, _ => instance); } - public static Task RaiseExceptionAsync(this AsyncPolicy policy, Action configureException = null) where TException : Exception, new() => - policy.RaiseExceptionAsync(1, configureException); + public static Task RaiseExceptionAsync(this AsyncPolicy policy, Action configureException = null) where TException : Exception, new() + { + return policy.RaiseExceptionAsync(1, configureException); + } public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() { diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs index 861c4f64aee..e8a48810cdf 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs @@ -8,8 +8,10 @@ namespace Polly.Specs.Helpers; public static class PolicyTResultExtensions { - public static TResult RaiseResultSequence(this Policy policy, params TResult[] resultsToRaise) => - policy.RaiseResultSequence(resultsToRaise.ToList()); + public static TResult RaiseResultSequence(this Policy policy, params TResult[] resultsToRaise) + { + return policy.RaiseResultSequence(resultsToRaise.ToList()); + } public static TResult RaiseResultSequence(this Policy policy, IEnumerable resultsToRaise) { @@ -27,8 +29,10 @@ public static TResult RaiseResultSequence(this Policy policy, } } - public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, params object[] resultsOrExceptionsToRaise) => - policy.RaiseResultAndOrExceptionSequence(resultsOrExceptionsToRaise.ToList()); + public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, params object[] resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequence(resultsOrExceptionsToRaise.ToList()); + } public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, IEnumerable resultsOrExceptionsToRaise) @@ -69,9 +73,11 @@ public class ResultAndOrCancellationScenario public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, - params TResult[] resultsToRaise) => - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, resultsToRaise.ToList()); + } public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, IEnumerable resultsToRaise) { diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs index fe96ee5132a..91b179ff5ad 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs @@ -10,11 +10,15 @@ namespace Polly.Specs.Helpers; public static class PolicyTResultExtensionsAsync { - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, params TResult[] resultsToRaise) => - policy.RaiseResultSequenceAsync(resultsToRaise.ToList()); + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAsync(resultsToRaise.ToList()); + } - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IEnumerable resultsToRaise) => - policy.RaiseResultSequenceAsync(default, resultsToRaise); + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IEnumerable resultsToRaise) + { + return policy.RaiseResultSequenceAsync(default, resultsToRaise); + } public static async Task RaiseResultSequenceAsync(this AsyncPolicy policy, CancellationToken cancellationToken, IEnumerable resultsToRaise) @@ -33,12 +37,16 @@ public static async Task RaiseResultSequenceAsync(this AsyncPo } } - public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, params object[] resultsOrExceptionsToRaise) => - policy.RaiseResultAndOrExceptionSequenceAsync(resultsOrExceptionsToRaise.ToList()); + public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, params object[] resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequenceAsync(resultsOrExceptionsToRaise.ToList()); + } public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, - IEnumerable resultsOrExceptionsToRaise) => - policy.RaiseResultAndOrExceptionSequenceAsync(CancellationToken.None, resultsOrExceptionsToRaise); + IEnumerable resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequenceAsync(CancellationToken.None, resultsOrExceptionsToRaise); + } public static async Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, CancellationToken cancellationToken, IEnumerable resultsOrExceptionsToRaise) @@ -79,9 +87,11 @@ public class ResultAndOrCancellationScenario public static Task RaiseResultSequenceAndOrCancellationAsync(this AsyncPolicy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, - params TResult[] resultsToRaise) => - policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, resultsToRaise.ToList()); + } public static async Task RaiseResultSequenceAndOrCancellationAsync( this AsyncPolicy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, diff --git a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs index 2a0a7200af9..f14a2821ccf 100644 --- a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs +++ b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs @@ -16,12 +16,16 @@ public void Dispose() SystemClock.Reset(); } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) => - Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); - - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) => - Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); + } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + } + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { if (policy is AsyncRateLimitPolicy typedPolicy) diff --git a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs index acd8794f747..6d362e9940e 100644 --- a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs +++ b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs @@ -11,18 +11,26 @@ namespace Polly.Specs.RateLimit; [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] public class AsyncRateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable { - public void Dispose() => + public void Dispose() + { SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) => - Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) => - Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + } protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, - Func retryAfterFactory) => - Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + Func retryAfterFactory) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + } protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs b/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs index c6ea7262503..0e32c4e2653 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs @@ -15,11 +15,15 @@ public void Dispose() SystemClock.Reset(); } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) => - Policy.RateLimit(numberOfExecutions, perTimeSpan); + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) => - Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + } protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs index 9e9fa4b7f77..7b54699707b 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs @@ -15,15 +15,21 @@ public void Dispose() SystemClock.Reset(); } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) => - Policy.RateLimit(numberOfExecutions, perTimeSpan); + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) => - Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + } protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, - Func retryAfterFactory) => - Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + Func retryAfterFactory) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + } protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { diff --git a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs index b6f3b99b9d8..41c01a764e4 100644 --- a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs @@ -12,10 +12,12 @@ public class ReadOnlyPolicyRegistrySpecs { IPolicyRegistry _registry; - IReadOnlyPolicyRegistry ReadOnlyRegistry => _registry; + IReadOnlyPolicyRegistry ReadOnlyRegistry { get{ return _registry; } } - public ReadOnlyPolicyRegistrySpecs() => + public ReadOnlyPolicyRegistrySpecs() + { _registry = new PolicyRegistry(); + } #region Tests for retrieving policy diff --git a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs b/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs index 54633b4cb2c..c7c5c00ce09 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs @@ -39,8 +39,10 @@ internal AsyncBulkheadPolicy( /// [DebuggerStepThrough] protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + bool continueOnCapturedContext) + { + return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + } /// public void Dispose() @@ -74,8 +76,10 @@ internal AsyncBulkheadPolicy( /// [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) => - AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + } /// /// Gets the number of slots currently available for executing actions through the bulkhead. diff --git a/src/Polly/Caching/AsyncCachePolicy.cs b/src/Polly/Caching/AsyncCachePolicy.cs index d1945b1e8c0..dd3ae0fc751 100644 --- a/src/Polly/Caching/AsyncCachePolicy.cs +++ b/src/Polly/Caching/AsyncCachePolicy.cs @@ -46,15 +46,18 @@ protected override Task ImplementationAsync( Func action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => + bool continueOnCapturedContext) + { // Pass-through/NOOP policy action, for void-returning executions through the cache policy. - action(context, cancellationToken); + return action(context, cancellationToken); + } /// [DebuggerStepThrough] protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncCacheEngine.ImplementationAsync( + bool continueOnCapturedContext) + { + return AsyncCacheEngine.ImplementationAsync( _asyncCacheProvider.AsyncFor(), _ttlStrategy.For(), _cacheKeyStrategy, @@ -67,6 +70,7 @@ protected override Task ImplementationAsync(Func @@ -109,8 +113,9 @@ internal AsyncCachePolicy( /// [DebuggerStepThrough] protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncCacheEngine.ImplementationAsync( + bool continueOnCapturedContext) + { + return AsyncCacheEngine.ImplementationAsync( _asyncCacheProvider, _ttlStrategy, _cacheKeyStrategy, @@ -123,4 +128,5 @@ protected override Task ImplementationAsync(Func action /// [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) => - CacheEngine.Implementation( + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return CacheEngine.Implementation( _syncCacheProvider.For(), _ttlStrategy.For(), _cacheKeyStrategy, @@ -60,6 +61,7 @@ protected override TResult Implementation(Func diff --git a/src/Polly/CircuitBreaker/BrokenCircuitException.cs b/src/Polly/CircuitBreaker/BrokenCircuitException.cs index ef2e35c6bd5..b19d7aef0b5 100644 --- a/src/Polly/CircuitBreaker/BrokenCircuitException.cs +++ b/src/Polly/CircuitBreaker/BrokenCircuitException.cs @@ -81,7 +81,7 @@ public BrokenCircuitException(string message, TResult result) : base(message) /// /// The result value which was considered a handled fault, by the policy. /// - public TResult Result => result; + public TResult Result { get => result; } #if NETSTANDARD2_0 /// diff --git a/src/Polly/CircuitBreaker/CircuitStateController.cs b/src/Polly/CircuitBreaker/CircuitStateController.cs index da937a5e731..2c32488ad1a 100644 --- a/src/Polly/CircuitBreaker/CircuitStateController.cs +++ b/src/Polly/CircuitBreaker/CircuitStateController.cs @@ -18,9 +18,9 @@ internal abstract class CircuitStateController : ICircuitController, CircuitState, TimeSpan, Context> onBreak, - Action onReset, + TimeSpan durationOfBreak, + Action, CircuitState, TimeSpan, Context> onBreak, + Action onReset, Action onHalfOpen) { _durationOfBreak = durationOfBreak; @@ -76,8 +76,13 @@ public TResult LastHandledResult } } - protected bool IsInAutomatedBreak_NeedsLock => - SystemClock.UtcNow().Ticks < _blockedTill; + protected bool IsInAutomatedBreak_NeedsLock + { + get + { + return SystemClock.UtcNow().Ticks < _blockedTill; + } + } public void Isolate() { diff --git a/src/Polly/CircuitBreaker/HealthCount.cs b/src/Polly/CircuitBreaker/HealthCount.cs index 093184593e5..887b53b30fa 100644 --- a/src/Polly/CircuitBreaker/HealthCount.cs +++ b/src/Polly/CircuitBreaker/HealthCount.cs @@ -6,7 +6,7 @@ internal class HealthCount public int Failures { get; set; } - public int Total => Successes + Failures; + public int Total { get { return Successes + Failures; } } public long StartedAt { get; set; } } \ No newline at end of file diff --git a/src/Polly/PolicyBuilder.cs b/src/Polly/PolicyBuilder.cs index 8ebf7e7405c..9586521f798 100644 --- a/src/Polly/PolicyBuilder.cs +++ b/src/Polly/PolicyBuilder.cs @@ -28,8 +28,10 @@ internal PolicyBuilder(ExceptionPredicate exceptionPredicate) /// A that represents this instance. /// [EditorBrowsable(EditorBrowsableState.Never)] - public override string ToString() => - base.ToString(); + public override string ToString() + { + return base.ToString(); + } /// /// Determines whether the specified is equal to this instance. @@ -39,8 +41,10 @@ public override string ToString() => /// true if the specified is equal to this instance; otherwise, false. /// [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => - base.Equals(obj); + public override bool Equals(object obj) + { + return base.Equals(obj); + } /// /// Returns a hash code for this instance. @@ -49,8 +53,10 @@ public override bool Equals(object obj) => /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => - base.GetHashCode(); + public override int GetHashCode() + { + return base.GetHashCode(); + } /// /// Gets the of the current instance. @@ -59,8 +65,10 @@ public override int GetHashCode() => /// The instance that represents the exact runtime type of the current instance. /// [EditorBrowsable(EditorBrowsableState.Never)] - public new Type GetType() => - base.GetType(); + public new Type GetType() + { + return base.GetType(); + } #endregion } diff --git a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs index c468741f927..ef414a5ce6a 100644 --- a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs @@ -13,8 +13,10 @@ public partial class Policy /// The policy instance. public static AsyncRateLimitPolicy RateLimitAsync( int numberOfExecutions, - TimeSpan perTimeSpan) => - RateLimitAsync(numberOfExecutions, perTimeSpan, 1); + TimeSpan perTimeSpan) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, 1); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given. diff --git a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs index b264da5a4a1..f18086f62ae 100644 --- a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs @@ -14,8 +14,10 @@ public partial class Policy /// The policy instance. public static AsyncRateLimitPolicy RateLimitAsync( int numberOfExecutions, - TimeSpan perTimeSpan) => - RateLimitAsync(numberOfExecutions, perTimeSpan, null); + TimeSpan perTimeSpan) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, null); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given. @@ -29,8 +31,10 @@ public static AsyncRateLimitPolicy RateLimitAsync( public static AsyncRateLimitPolicy RateLimitAsync( int numberOfExecutions, TimeSpan perTimeSpan, - Func retryAfterFactory) => - RateLimitAsync(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + Func retryAfterFactory) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given. @@ -44,8 +48,10 @@ public static AsyncRateLimitPolicy RateLimitAsync( public static AsyncRateLimitPolicy RateLimitAsync( int numberOfExecutions, TimeSpan perTimeSpan, - int maxBurst) => - RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, null); + int maxBurst) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, null); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given, diff --git a/src/Polly/RateLimit/RateLimitSyntax.cs b/src/Polly/RateLimit/RateLimitSyntax.cs index 287d1ff75f5..ad7001678ce 100644 --- a/src/Polly/RateLimit/RateLimitSyntax.cs +++ b/src/Polly/RateLimit/RateLimitSyntax.cs @@ -13,8 +13,10 @@ public partial class Policy /// The policy instance. public static RateLimitPolicy RateLimit( int numberOfExecutions, - TimeSpan perTimeSpan) => - RateLimit(numberOfExecutions, perTimeSpan, 1); + TimeSpan perTimeSpan) + { + return RateLimit(numberOfExecutions, perTimeSpan, 1); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given. diff --git a/src/Polly/RateLimit/RateLimitTResultSyntax.cs b/src/Polly/RateLimit/RateLimitTResultSyntax.cs index 96077ed2c58..9fb540a2d1e 100644 --- a/src/Polly/RateLimit/RateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/RateLimitTResultSyntax.cs @@ -14,8 +14,10 @@ public partial class Policy /// The policy instance. public static RateLimitPolicy RateLimit( int numberOfExecutions, - TimeSpan perTimeSpan) => - RateLimit(numberOfExecutions, perTimeSpan, null); + TimeSpan perTimeSpan) + { + return RateLimit(numberOfExecutions, perTimeSpan, null); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given. @@ -29,8 +31,10 @@ public static RateLimitPolicy RateLimit( public static RateLimitPolicy RateLimit( int numberOfExecutions, TimeSpan perTimeSpan, - Func retryAfterFactory) => - RateLimit(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + Func retryAfterFactory) + { + return RateLimit(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given. @@ -44,8 +48,10 @@ public static RateLimitPolicy RateLimit( public static RateLimitPolicy RateLimit( int numberOfExecutions, TimeSpan perTimeSpan, - int maxBurst) => - RateLimit(numberOfExecutions, perTimeSpan, maxBurst, null); + int maxBurst) + { + return RateLimit(numberOfExecutions, perTimeSpan, maxBurst, null); + } /// /// Builds a RateLimit that will rate-limit executions to per the timespan given, diff --git a/src/Polly/Timeout/AsyncTimeoutPolicy.cs b/src/Polly/Timeout/AsyncTimeoutPolicy.cs index eb09ff6becf..0f810a074cd 100644 --- a/src/Polly/Timeout/AsyncTimeoutPolicy.cs +++ b/src/Polly/Timeout/AsyncTimeoutPolicy.cs @@ -31,8 +31,9 @@ protected override Task ImplementationAsync( Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) => - AsyncTimeoutEngine.ImplementationAsync( + bool continueOnCapturedContext) + { + return AsyncTimeoutEngine.ImplementationAsync( action, context, cancellationToken, @@ -40,6 +41,7 @@ protected override Task ImplementationAsync( _timeoutStrategy, _onTimeoutAsync, continueOnCapturedContext); + } } /// diff --git a/src/Polly/Utilities/TimedLock.cs b/src/Polly/Utilities/TimedLock.cs index db9952a3599..c00f69a49f3 100644 --- a/src/Polly/Utilities/TimedLock.cs +++ b/src/Polly/Utilities/TimedLock.cs @@ -24,8 +24,10 @@ internal struct TimedLock : IDisposable private static readonly TimeSpan LockTimeout = TimeSpan.FromMilliseconds(int.MaxValue); #endif - public static TimedLock Lock(object o) => - Lock(o, LockTimeout); + public static TimedLock Lock(object o) + { + return Lock(o, LockTimeout); + } private static TimedLock Lock(object o, TimeSpan timeout) { From 63b151ebc191ef35df9e3f29584ca94ec5d7c792 Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 14:52:31 +0000 Subject: [PATCH 19/24] Revert "file scoped namespaces" This reverts commit d05f172f77b7d7383ce04ac7f0660ca7dc6985be. --- src/Polly.Benchmarks/Bulkhead.cs | 83 +- src/Polly.Benchmarks/Cache.cs | 147 +- src/Polly.Benchmarks/CircuitBreaker.cs | 53 +- src/Polly.Benchmarks/Fallback.cs | 53 +- src/Polly.Benchmarks/NoOp.cs | 53 +- src/Polly.Benchmarks/PolicyWrap.cs | 69 +- src/Polly.Benchmarks/PollyConfig.cs | 41 +- src/Polly.Benchmarks/Retry.cs | 117 +- src/Polly.Benchmarks/Timeout.cs | 95 +- src/Polly.Benchmarks/Workloads.cs | 61 +- .../Bulkhead/BulkheadAsyncSpecs.cs | 129 +- src/Polly.Specs/Bulkhead/BulkheadScenario.cs | 45 +- src/Polly.Specs/Bulkhead/BulkheadScenarios.cs | 37 +- src/Polly.Specs/Bulkhead/BulkheadSpecs.cs | 129 +- src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs | 549 +- .../Bulkhead/BulkheadTResultAsyncSpecs.cs | 149 +- .../Bulkhead/BulkheadTResultSpecs.cs | 149 +- .../Bulkhead/IBulkheadPolicySpecs.cs | 29 +- src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs | 103 +- src/Polly.Specs/Caching/CacheAsyncSpecs.cs | 1025 ++-- src/Polly.Specs/Caching/CacheSpecs.cs | 1007 +-- .../Caching/CacheTResultAsyncSpecs.cs | 705 +-- src/Polly.Specs/Caching/CacheTResultSpecs.cs | 683 +- src/Polly.Specs/Caching/ContextualTtlSpecs.cs | 87 +- .../Caching/DefaultCacheKeyStrategySpecs.cs | 21 +- .../Caching/GenericCacheProviderAsyncSpecs.cs | 89 +- .../Caching/GenericCacheProviderSpecs.cs | 73 +- src/Polly.Specs/Caching/RelativeTtlSpecs.cs | 83 +- src/Polly.Specs/Caching/ResultTtlSpecs.cs | 93 +- .../SerializingCacheProviderAsyncSpecs.cs | 879 +-- .../Caching/SerializingCacheProviderSpecs.cs | 871 +-- src/Polly.Specs/Caching/SlidingTtlSpecs.cs | 59 +- .../AdvancedCircuitBreakerAsyncSpecs.cs | 5465 +++++++++-------- .../AdvancedCircuitBreakerSpecs.cs | 5378 ++++++++-------- .../CircuitBreakerAsyncSpecs.cs | 2571 ++++---- .../CircuitBreaker/CircuitBreakerSpecs.cs | 2627 ++++---- .../CircuitBreakerTResultAsyncSpecs.cs | 2431 ++++---- ...BreakerTResultMixedResultExceptionSpecs.cs | 1025 ++-- .../CircuitBreakerTResultSpecs.cs | 2415 ++++---- .../ICircuitBreakerPolicySpecs.cs | 79 +- .../ICircuitBreakerPolicyTResultSpecs.cs | 23 +- src/Polly.Specs/ContextSpecs.cs | 75 +- src/Polly.Specs/Custom/CustomAsyncSpecs.cs | 145 +- src/Polly.Specs/Custom/CustomSpecs.cs | 119 +- .../Custom/CustomTResultAsyncSpecs.cs | 173 +- src/Polly.Specs/Custom/CustomTResultSpecs.cs | 131 +- .../Fallback/FallbackAsyncSpecs.cs | 1177 ++-- src/Polly.Specs/Fallback/FallbackSpecs.cs | 1823 +++--- .../Fallback/FallbackTResultAsyncSpecs.cs | 1205 ++-- .../Fallback/FallbackTResultSpecs.cs | 1227 ++-- .../Helpers/Bulkhead/AnnotatedOutputHelper.cs | 81 +- .../Helpers/Bulkhead/AssertionFailure.cs | 27 +- .../Helpers/Bulkhead/SilentOutputHelper.cs | 21 +- .../Helpers/Bulkhead/TraceableAction.cs | 293 +- .../Helpers/Bulkhead/TraceableActionStatus.cs | 29 +- .../Helpers/Caching/StubCacheKeyStrategy.cs | 31 +- .../Helpers/Caching/StubCacheProvider.cs | 87 +- .../Caching/StubErroringCacheProvider.cs | 69 +- .../Helpers/Caching/StubSerialized.cs | 37 +- .../Helpers/Caching/StubSerializer.cs | 37 +- src/Polly.Specs/Helpers/Constants.cs | 25 +- .../Helpers/ContextualPolicyExtensions.cs | 51 +- .../ContextualPolicyExtensionsAsync.cs | 41 +- .../ContextualPolicyTResultExtensions.cs | 87 +- .../ContextualPolicyTResultExtensionsAsync.cs | 71 +- .../AddBehaviourIfHandleEngine.cs | 57 +- .../AddBehaviourIfHandlePolicy.cs | 89 +- .../AddBehaviourIfHandleSyntax.cs | 25 +- .../AsyncAddBehaviourIfHandleEngine.cs | 59 +- .../AsyncAddBehaviourIfHandlePolicy.cs | 99 +- .../AsyncAddBehaviourIfHandleSyntax.cs | 33 +- .../PreExecute/AsyncPreExecuteEngine.cs | 43 +- .../PreExecute/AsyncPreExecutePolicy.cs | 73 +- .../Custom/PreExecute/PreExecuteEngine.cs | 39 +- .../Custom/PreExecute/PreExecutePolicy.cs | 69 +- src/Polly.Specs/Helpers/ObjectExtensions.cs | 19 +- src/Polly.Specs/Helpers/PolicyExtensions.cs | 173 +- .../Helpers/PolicyExtensionsAsync.cs | 175 +- .../Helpers/PolicyTResultExtensions.cs | 164 +- .../Helpers/PolicyTResultExtensionsAsync.cs | 189 +- .../RateLimit/IRateLimiterExtensions.cs | 49 +- .../RateLimit/ResultClassWithRetryAfter.cs | 25 +- src/Polly.Specs/Helpers/ResultClass.cs | 33 +- src/Polly.Specs/Helpers/ResultPrimitive.cs | 31 +- .../IAsyncPolicyExtensionsSpecs.cs | 44 +- src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs | 44 +- src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs | 49 +- src/Polly.Specs/NoOp/NoOpSpecs.cs | 49 +- src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs | 57 +- src/Polly.Specs/NoOp/NoOpTResultSpecs.cs | 53 +- src/Polly.Specs/PolicyAsyncSpecs.cs | 495 +- .../PolicyContextAndKeyAsyncSpecs.cs | 479 +- src/Polly.Specs/PolicyContextAndKeySpecs.cs | 473 +- src/Polly.Specs/PolicySpecs.cs | 485 +- src/Polly.Specs/PolicyTResultAsyncSpecs.cs | 285 +- src/Polly.Specs/PolicyTResultSpecs.cs | 291 +- .../RateLimit/AsyncRateLimitPolicySpecs.cs | 57 +- .../AsyncRateLimitPolicyTResultSpecs.cs | 85 +- .../LockFreeTokenBucketRateLimiterTests.cs | 13 +- .../RateLimit/RateLimitPolicySpecs.cs | 55 +- .../RateLimit/RateLimitPolicySpecsBase.cs | 473 +- .../RateLimit/RateLimitPolicyTResultSpecs.cs | 85 +- .../RateLimitPolicyTResultSpecsBase.cs | 91 +- .../RateLimit/RateLimitSpecsBase.cs | 73 +- .../TokenBucketRateLimiterTestsBase.cs | 325 +- .../Registry/ConcurrentPolicyRegistrySpecs.cs | 425 +- .../Registry/PolicyRegistrySpecs.cs | 849 +-- .../Registry/ReadOnlyPolicyRegistrySpecs.cs | 461 +- src/Polly.Specs/Retry/RetryAsyncSpecs.cs | 1121 ++-- .../Retry/RetryForeverAsyncSpecs.cs | 781 +-- src/Polly.Specs/Retry/RetryForeverSpecs.cs | 487 +- src/Polly.Specs/Retry/RetrySpecs.cs | 1281 ++-- .../RetryTResultMixedResultExceptionSpecs.cs | 379 +- src/Polly.Specs/Retry/RetryTResultSpecs.cs | 1169 ++-- .../Retry/RetryTResultSpecsAsync.cs | 1201 ++-- .../Retry/WaitAndRetryAsyncSpecs.cs | 1679 ++--- .../Retry/WaitAndRetryForeverAsyncSpecs.cs | 1025 ++-- .../Retry/WaitAndRetryForeverSpecs.cs | 559 +- .../WaitAndRetryForeverTResultAsyncSpecs.cs | 83 +- .../Retry/WaitAndRetryForeverTResultSpecs.cs | 71 +- src/Polly.Specs/Retry/WaitAndRetrySpecs.cs | 1841 +++--- .../Retry/WaitAndRetryTResultAsyncSpecs.cs | 83 +- .../Retry/WaitAndRetryTResultSpecs.cs | 71 +- src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs | 1121 ++-- src/Polly.Specs/Timeout/TimeoutSpecs.cs | 1199 ++-- src/Polly.Specs/Timeout/TimeoutSpecsBase.cs | 153 +- .../Timeout/TimeoutTResultAsyncSpecs.cs | 1069 ++-- .../Timeout/TimeoutTResultSpecs.cs | 1165 ++-- .../Wrap/IPolicyWrapExtensionSpecs.cs | 511 +- .../Wrap/PolicyWrapContextAndKeySpecs.cs | 452 +- .../Wrap/PolicyWrapContextAndKeySpecsAsync.cs | 568 +- src/Polly.Specs/Wrap/PolicyWrapSpecs.cs | 959 +-- src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs | 965 +-- src/Polly/AsyncPolicy.ContextAndKeys.cs | 105 +- src/Polly/AsyncPolicy.ExecuteOverloads.cs | 915 +-- .../AsyncPolicy.GenericImplementation.cs | 37 +- .../AsyncPolicy.NonGenericImplementation.cs | 75 +- .../AsyncPolicy.TResult.ExecuteOverloads.cs | 463 +- src/Polly/AsyncPolicy.TResult.cs | 47 +- src/Polly/AsyncPolicy.cs | 39 +- src/Polly/Bulkhead/AsyncBulkheadEngine.cs | 55 +- src/Polly/Bulkhead/AsyncBulkheadPolicy.cs | 157 +- src/Polly/Bulkhead/AsyncBulkheadSyntax.cs | 129 +- .../Bulkhead/AsyncBulkheadTResultSyntax.cs | 121 +- src/Polly/Bulkhead/BulkheadEngine.cs | 49 +- src/Polly/Bulkhead/BulkheadPolicy.cs | 159 +- .../Bulkhead/BulkheadSemaphoreFactory.cs | 23 +- src/Polly/Bulkhead/BulkheadSyntax.cs | 125 +- src/Polly/Bulkhead/BulkheadTResultSyntax.cs | 125 +- src/Polly/Bulkhead/IBulkheadPolicy.cs | 38 +- src/Polly/Caching/AbsoluteTtl.cs | 21 +- src/Polly/Caching/AsyncCacheEngine.cs | 107 +- src/Polly/Caching/AsyncCachePolicy.cs | 228 +- src/Polly/Caching/AsyncCacheSyntax.cs | 611 +- src/Polly/Caching/AsyncCacheTResultSyntax.cs | 1633 ++--- .../Caching/AsyncGenericCacheProvider.cs | 37 +- .../Caching/AsyncSerializingCacheProvider.cs | 217 +- src/Polly/Caching/CacheEngine.cs | 105 +- src/Polly/Caching/CachePolicy.cs | 205 +- src/Polly/Caching/CacheProviderExtensions.cs | 127 +- src/Polly/Caching/CacheSyntax.cs | 635 +- src/Polly/Caching/CacheTResultSyntax.cs | 1643 ++--- src/Polly/Caching/ContextualTtl.cs | 65 +- src/Polly/Caching/DefaultCacheKeyStrategy.cs | 31 +- src/Polly/Caching/GenericCacheProvider.cs | 37 +- src/Polly/Caching/GenericTtlStrategy.cs | 35 +- src/Polly/Caching/IAsyncCacheProvider.cs | 101 +- src/Polly/Caching/ICacheItemSerializer.cs | 39 +- src/Polly/Caching/ICacheKeyStrategy.cs | 23 +- src/Polly/Caching/ICachePolicy.cs | 28 +- src/Polly/Caching/ISyncCacheProvider.cs | 81 +- src/Polly/Caching/ITtlStrategy.cs | 37 +- src/Polly/Caching/NonSlidingTtl.cs | 53 +- src/Polly/Caching/RelativeTtl.cs | 45 +- src/Polly/Caching/ResultTtl.cs | 59 +- src/Polly/Caching/SerializingCacheProvider.cs | 171 +- src/Polly/Caching/SlidingTtl.cs | 46 +- src/Polly/Caching/Ttl.cs | 61 +- src/Polly/Caching/TtlStrategyExtensions.cs | 27 +- .../AdvancedCircuitBreakerSyntax.cs | 465 +- .../AdvancedCircuitBreakerTResultSyntax.cs | 469 +- .../AdvancedCircuitController.cs | 157 +- .../AsyncAdvancedCircuitBreakerSyntax.cs | 473 +- ...syncAdvancedCircuitBreakerTResultSyntax.cs | 469 +- .../AsyncCircuitBreakerEngine.cs | 72 +- .../AsyncCircuitBreakerPolicy.cs | 205 +- .../AsyncCircuitBreakerSyntax.cs | 400 +- .../AsyncCircuitBreakerTResultSyntax.cs | 400 +- .../CircuitBreaker/CircuitBreakerEngine.cs | 75 +- .../CircuitBreaker/CircuitBreakerPolicy.cs | 189 +- .../CircuitBreaker/CircuitBreakerSyntax.cs | 399 +- .../CircuitBreakerTResultSyntax.cs | 399 +- src/Polly/CircuitBreaker/CircuitState.cs | 43 +- .../CircuitBreaker/CircuitStateController.cs | 258 +- .../ConsecutiveCountCircuitController.cs | 117 +- src/Polly/CircuitBreaker/HealthCount.cs | 17 +- .../CircuitBreaker/ICircuitBreakerPolicy.cs | 63 +- .../CircuitBreaker/ICircuitController.cs | 27 +- src/Polly/CircuitBreaker/IHealthMetrics.cs | 17 +- .../CircuitBreaker/RollingHealthMetrics.cs | 107 +- .../CircuitBreaker/SingleHealthMetrics.cs | 57 +- src/Polly/Context.Dictionary.cs | 189 +- src/Polly/Context.cs | 89 +- src/Polly/DelegateResult.cs | 47 +- src/Polly/ExceptionPredicate.cs | 17 +- src/Polly/ExceptionPredicates.cs | 48 +- src/Polly/Fallback/AsyncFallbackEngine.cs | 69 +- src/Polly/Fallback/AsyncFallbackPolicy.cs | 143 +- src/Polly/Fallback/AsyncFallbackSyntax.cs | 401 +- src/Polly/Fallback/FallbackEngine.cs | 67 +- src/Polly/Fallback/FallbackPolicy.cs | 117 +- src/Polly/Fallback/FallbackSyntax.cs | 563 +- src/Polly/Fallback/IFallbackPolicy.cs | 28 +- src/Polly/IAsyncPolicy.Extensions.cs | 28 +- src/Polly/IAsyncPolicy.TResult.cs | 355 +- src/Polly/IAsyncPolicy.cs | 683 +- src/Polly/ISyncPolicy.Extensions.cs | 28 +- src/Polly/ISyncPolicy.TResult.cs | 222 +- src/Polly/ISyncPolicy.cs | 451 +- src/Polly/IsPolicy.cs | 21 +- src/Polly/NoOp/AsyncNoOpPolicy.cs | 53 +- src/Polly/NoOp/AsyncNoOpSyntax.cs | 19 +- src/Polly/NoOp/AsyncNoOpTResultSyntax.cs | 21 +- src/Polly/NoOp/INoOpPolicy.cs | 28 +- src/Polly/NoOp/NoOpEngine.cs | 13 +- src/Polly/NoOp/NoOpEngineAsync.cs | 13 +- src/Polly/NoOp/NoOpPolicy.cs | 53 +- src/Polly/NoOp/NoOpSyntax.cs | 19 +- src/Polly/NoOp/NoOpTResultSyntax.cs | 21 +- src/Polly/Policy.ContextAndKeys.cs | 105 +- src/Polly/Policy.ExecuteOverloads.cs | 629 +- src/Polly/Policy.HandleSyntax.cs | 195 +- src/Polly/Policy.SyncGenericImplementation.cs | 33 +- .../Policy.SyncNonGenericImplementation.cs | 45 +- src/Polly/Policy.TResult.ExecuteOverloads.cs | 331 +- src/Polly/Policy.TResult.cs | 41 +- src/Polly/Policy.cs | 37 +- src/Polly/PolicyBase.ContextAndKeys.cs | 69 +- src/Polly/PolicyBase.cs | 123 +- src/Polly/PolicyBuilder.OrSyntax.cs | 329 +- src/Polly/PolicyBuilder.cs | 267 +- src/Polly/PolicyResult.cs | 371 +- src/Polly/RateLimit/AsyncRateLimitEngine.cs | 45 +- src/Polly/RateLimit/AsyncRateLimitPolicy.cs | 71 +- src/Polly/RateLimit/AsyncRateLimitSyntax.cs | 77 +- .../RateLimit/AsyncRateLimitTResultSyntax.cs | 153 +- src/Polly/RateLimit/IRateLimitPolicy.cs | 27 +- src/Polly/RateLimit/IRateLimiter.cs | 21 +- .../LockFreeTokenBucketRateLimiter.cs | 185 +- src/Polly/RateLimit/RateLimitEngine.cs | 41 +- src/Polly/RateLimit/RateLimitPolicy.cs | 67 +- src/Polly/RateLimit/RateLimitSyntax.cs | 77 +- src/Polly/RateLimit/RateLimitTResultSyntax.cs | 153 +- src/Polly/RateLimit/RateLimiterFactory.cs | 13 +- .../Registry/IConcurrentPolicyRegistry.cs | 157 +- src/Polly/Registry/IPolicyRegistry.cs | 71 +- src/Polly/Registry/IReadOnlyPolicyRegistry.cs | 89 +- src/Polly/Registry/PolicyRegistry.cs | 465 +- src/Polly/ResultPredicate.cs | 18 +- src/Polly/ResultPredicates.cs | 50 +- src/Polly/Retry/AsyncRetryEngine.cs | 121 +- src/Polly/Retry/AsyncRetryPolicy.cs | 166 +- src/Polly/Retry/AsyncRetrySyntax.cs | 2120 +++---- src/Polly/Retry/AsyncRetryTResultSyntax.cs | 2116 +++---- src/Polly/Retry/IRetryPolicy.cs | 28 +- src/Polly/Retry/RetryEngine.cs | 123 +- src/Polly/Retry/RetryPolicy.cs | 153 +- src/Polly/Retry/RetrySyntax.cs | 1245 ++-- src/Polly/Retry/RetryTResultSyntax.cs | 1327 ++-- src/Polly/Timeout/AsyncTimeoutEngine.cs | 105 +- src/Polly/Timeout/AsyncTimeoutPolicy.cs | 137 +- src/Polly/Timeout/AsyncTimeoutSyntax.cs | 765 +-- .../Timeout/AsyncTimeoutTResultSyntax.cs | 753 +-- src/Polly/Timeout/ITimeoutPolicy.cs | 28 +- src/Polly/Timeout/TimeoutEngine.cs | 93 +- src/Polly/Timeout/TimeoutPolicy.cs | 109 +- src/Polly/Timeout/TimeoutStrategy.cs | 27 +- src/Polly/Timeout/TimeoutSyntax.cs | 751 +-- src/Polly/Timeout/TimeoutTResultSyntax.cs | 765 +-- src/Polly/Timeout/TimeoutValidator.cs | 27 +- src/Polly/Utilities/EmptyStruct.cs | 17 +- src/Polly/Utilities/ExceptionExtensions.cs | 27 +- src/Polly/Utilities/KeyHelper.cs | 11 +- src/Polly/Utilities/SystemClock.cs | 89 +- src/Polly/Utilities/TaskHelper.cs | 21 +- src/Polly/Utilities/TimedLock.cs | 120 +- .../Wrap/AsyncPolicyWrap.ContextAndKeys.cs | 59 +- src/Polly/Wrap/AsyncPolicyWrap.cs | 287 +- src/Polly/Wrap/AsyncPolicyWrapEngine.cs | 179 +- src/Polly/Wrap/AsyncPolicyWrapSyntax.cs | 281 +- src/Polly/Wrap/IPolicyWrap.cs | 37 +- src/Polly/Wrap/IPolicyWrapExtension.cs | 125 +- src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs | 59 +- src/Polly/Wrap/PolicyWrap.cs | 263 +- src/Polly/Wrap/PolicyWrapEngine.cs | 79 +- src/Polly/Wrap/PolicyWrapSyntax.cs | 281 +- 296 files changed, 49751 insertions(+), 49427 deletions(-) diff --git a/src/Polly.Benchmarks/Bulkhead.cs b/src/Polly.Benchmarks/Bulkhead.cs index 70efe99d165..275d39a1681 100644 --- a/src/Polly.Benchmarks/Bulkhead.cs +++ b/src/Polly.Benchmarks/Bulkhead.cs @@ -2,47 +2,48 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class Bulkhead +namespace Polly.Benchmarks { - private static readonly Policy SyncPolicy = Policy.Bulkhead(2); - private static readonly AsyncPolicy AsyncPolicy = Policy.BulkheadAsync(2); - - [Benchmark] - public void Bulkhead_Synchronous() - { - SyncPolicy.Execute(() => Workloads.Action()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous() - { - await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous_With_CancellationToken() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } - - [Benchmark] - public int Bulkhead_Synchronous_With_Result() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous_With_Result() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } - - [Benchmark] - public async Task Bulkhead_Asynchronous_With_Result_With_CancellationToken() + [Config(typeof(PollyConfig))] + public class Bulkhead { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + private static readonly Policy SyncPolicy = Policy.Bulkhead(2); + private static readonly AsyncPolicy AsyncPolicy = Policy.BulkheadAsync(2); + + [Benchmark] + public void Bulkhead_Synchronous() + { + SyncPolicy.Execute(() => Workloads.Action()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous_With_CancellationToken() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } + + [Benchmark] + public int Bulkhead_Synchronous_With_Result() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous_With_Result() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } + + [Benchmark] + public async Task Bulkhead_Asynchronous_With_Result_With_CancellationToken() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/Cache.cs b/src/Polly.Benchmarks/Cache.cs index 58d3d37f5ba..3ef233a84af 100644 --- a/src/Polly.Benchmarks/Cache.cs +++ b/src/Polly.Benchmarks/Cache.cs @@ -5,106 +5,107 @@ using Microsoft.Extensions.Caching.Memory; using Polly.Caching; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class Cache +namespace Polly.Benchmarks { - private static readonly MemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions()); - private static readonly MemoryCacheProvider CacheProvider = new MemoryCacheProvider(MemoryCache); - - private static readonly Policy SyncPolicyMiss = Policy.Cache(CacheProvider, TimeSpan.Zero); - private static readonly AsyncPolicy AsyncPolicyMiss = Policy.CacheAsync(CacheProvider, TimeSpan.Zero); - - private static readonly Policy SyncPolicyHit = Policy.Cache(CacheProvider, TimeSpan.MaxValue); - private static readonly AsyncPolicy AsyncPolicyHit = Policy.CacheAsync(CacheProvider, TimeSpan.MaxValue); - - private static readonly Context HitContext = new Context(nameof(HitContext)); - private static readonly Context MissContext = new Context(nameof(MissContext)); - - [GlobalSetup] - public async Task GlobalSetup() + [Config(typeof(PollyConfig))] + public class Cache { - SyncPolicyHit.Execute((context) => GetObject(), HitContext); - await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); - } + private static readonly MemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions()); + private static readonly MemoryCacheProvider CacheProvider = new MemoryCacheProvider(MemoryCache); - [Benchmark] - public object Cache_Synchronous_Hit() - { - return SyncPolicyHit.Execute((context) => GetObject(), HitContext); - } - - [Benchmark] - public async Task Cache_Asynchronous_Hit() - { - return await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); - } + private static readonly Policy SyncPolicyMiss = Policy.Cache(CacheProvider, TimeSpan.Zero); + private static readonly AsyncPolicy AsyncPolicyMiss = Policy.CacheAsync(CacheProvider, TimeSpan.Zero); - [Benchmark] - public object Cache_Synchronous_Miss() - { - return SyncPolicyMiss.Execute((context) => GetObject(), MissContext); - } + private static readonly Policy SyncPolicyHit = Policy.Cache(CacheProvider, TimeSpan.MaxValue); + private static readonly AsyncPolicy AsyncPolicyHit = Policy.CacheAsync(CacheProvider, TimeSpan.MaxValue); - [Benchmark] - public async Task Cache_Asynchronous_Miss() - { - return await AsyncPolicyMiss.ExecuteAsync((context, token) => GetObjectAsync(token), MissContext, CancellationToken.None); - } + private static readonly Context HitContext = new Context(nameof(HitContext)); + private static readonly Context MissContext = new Context(nameof(MissContext)); - private static object GetObject() => new object(); + [GlobalSetup] + public async Task GlobalSetup() + { + SyncPolicyHit.Execute((context) => GetObject(), HitContext); + await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); + } - private static Task GetObjectAsync(CancellationToken cancellationToken) => Task.FromResult(new object()); + [Benchmark] + public object Cache_Synchronous_Hit() + { + return SyncPolicyHit.Execute((context) => GetObject(), HitContext); + } - private sealed class MemoryCacheProvider : ISyncCacheProvider, IAsyncCacheProvider - { - private readonly IMemoryCache _cache; + [Benchmark] + public async Task Cache_Asynchronous_Hit() + { + return await AsyncPolicyHit.ExecuteAsync((context, token) => GetObjectAsync(token), HitContext, CancellationToken.None); + } - public MemoryCacheProvider(IMemoryCache memoryCache) + [Benchmark] + public object Cache_Synchronous_Miss() { - _cache = memoryCache; + return SyncPolicyMiss.Execute((context) => GetObject(), MissContext); } - public (bool, object) TryGet(string key) + [Benchmark] + public async Task Cache_Asynchronous_Miss() { - var cacheHit = _cache.TryGetValue(key, out var value); - return (cacheHit, value); + return await AsyncPolicyMiss.ExecuteAsync((context, token) => GetObjectAsync(token), MissContext, CancellationToken.None); } - public void Put(string key, object value, Ttl ttl) + private static object GetObject() => new object(); + + private static Task GetObjectAsync(CancellationToken cancellationToken) => Task.FromResult(new object()); + + private sealed class MemoryCacheProvider : ISyncCacheProvider, IAsyncCacheProvider { - var remaining = DateTimeOffset.MaxValue - DateTimeOffset.UtcNow; - var options = new MemoryCacheEntryOptions(); + private readonly IMemoryCache _cache; + + public MemoryCacheProvider(IMemoryCache memoryCache) + { + _cache = memoryCache; + } - if (ttl.SlidingExpiration) + public (bool, object) TryGet(string key) { - options.SlidingExpiration = ttl.Timespan < remaining ? ttl.Timespan : remaining; + var cacheHit = _cache.TryGetValue(key, out var value); + return (cacheHit, value); } - else + + public void Put(string key, object value, Ttl ttl) { - if (ttl.Timespan == TimeSpan.MaxValue) + var remaining = DateTimeOffset.MaxValue - DateTimeOffset.UtcNow; + var options = new MemoryCacheEntryOptions(); + + if (ttl.SlidingExpiration) { - options.AbsoluteExpiration = DateTimeOffset.MaxValue; + options.SlidingExpiration = ttl.Timespan < remaining ? ttl.Timespan : remaining; } else { - options.AbsoluteExpirationRelativeToNow = ttl.Timespan < remaining ? ttl.Timespan : remaining; + if (ttl.Timespan == TimeSpan.MaxValue) + { + options.AbsoluteExpiration = DateTimeOffset.MaxValue; + } + else + { + options.AbsoluteExpirationRelativeToNow = ttl.Timespan < remaining ? ttl.Timespan : remaining; + } } - } - _cache.Set(key, value, options); - } + _cache.Set(key, value, options); + } - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return Task.FromResult(TryGet(key)); - } + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } - public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - Put(key, value, ttl); - return Task.CompletedTask; + public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + Put(key, value, ttl); + return Task.CompletedTask; + } } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/CircuitBreaker.cs b/src/Polly.Benchmarks/CircuitBreaker.cs index 277d991b92d..c439e948043 100644 --- a/src/Polly.Benchmarks/CircuitBreaker.cs +++ b/src/Polly.Benchmarks/CircuitBreaker.cs @@ -3,35 +3,36 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class CircuitBreaker +namespace Polly.Benchmarks { - private static readonly Policy SyncPolicy = Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)); - private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - [Benchmark] - public void CircuitBreaker_Synchronous_Succeeds() + [Config(typeof(PollyConfig))] + public class CircuitBreaker { - SyncPolicy.Execute(() => Workloads.Action()); - } + private static readonly Policy SyncPolicy = Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)); + private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Benchmark] - public async Task CircuitBreaker_Asynchronous_Succeeds() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public void CircuitBreaker_Synchronous_Succeeds() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public int CircuitBreaker_Synchronous_With_Result_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public async Task CircuitBreaker_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public async Task CircuitBreaker_Asynchronous_With_Result_Succeeds() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + [Benchmark] + public int CircuitBreaker_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } + + [Benchmark] + public async Task CircuitBreaker_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/Fallback.cs b/src/Polly.Benchmarks/Fallback.cs index 53e58eb937d..09395ebb0f1 100644 --- a/src/Polly.Benchmarks/Fallback.cs +++ b/src/Polly.Benchmarks/Fallback.cs @@ -2,35 +2,36 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class Fallback +namespace Polly.Benchmarks { - private static readonly Policy SyncPolicy = Policy.Handle().Fallback(0); - private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().FallbackAsync(0); - - [Benchmark] - public int Fallback_Synchronous_Succeeds() + [Config(typeof(PollyConfig))] + public class Fallback { - return SyncPolicy.Execute(() => Workloads.Func()); - } + private static readonly Policy SyncPolicy = Policy.Handle().Fallback(0); + private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().FallbackAsync(0); - [Benchmark] - public async Task Fallback_Asynchronous_Succeeds() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } + [Benchmark] + public int Fallback_Synchronous_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public int Fallback_Synchronous_Throws() - { - return SyncPolicy.Execute(() => Workloads.FuncThrows()); - } + [Benchmark] + public async Task Fallback_Asynchronous_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } - [Benchmark] - public async Task Fallback_Asynchronous_Throws() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncThrowsAsync()); + [Benchmark] + public int Fallback_Synchronous_Throws() + { + return SyncPolicy.Execute(() => Workloads.FuncThrows()); + } + + [Benchmark] + public async Task Fallback_Asynchronous_Throws() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncThrowsAsync()); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/NoOp.cs b/src/Polly.Benchmarks/NoOp.cs index 88dde9e91a0..eb5ab880bdd 100644 --- a/src/Polly.Benchmarks/NoOp.cs +++ b/src/Polly.Benchmarks/NoOp.cs @@ -2,35 +2,36 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class NoOp +namespace Polly.Benchmarks { - private static readonly Policy SyncPolicy = Policy.NoOp(); - private static readonly AsyncPolicy AsyncPolicy = Policy.NoOpAsync(); - - [Benchmark] - public void NoOp_Synchronous() + [Config(typeof(PollyConfig))] + public class NoOp { - SyncPolicy.Execute(() => Workloads.Action()); - } + private static readonly Policy SyncPolicy = Policy.NoOp(); + private static readonly AsyncPolicy AsyncPolicy = Policy.NoOpAsync(); - [Benchmark] - public async Task NoOp_Asynchronous() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public void NoOp_Synchronous() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public int NoOp_Synchronous_With_Result() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public async Task NoOp_Asynchronous() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public async Task NoOp_Asynchronous_With_Result() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + [Benchmark] + public int NoOp_Synchronous_With_Result() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } + + [Benchmark] + public async Task NoOp_Asynchronous_With_Result() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/PolicyWrap.cs b/src/Polly.Benchmarks/PolicyWrap.cs index ab245ce4b06..6d4280231f4 100644 --- a/src/Polly.Benchmarks/PolicyWrap.cs +++ b/src/Polly.Benchmarks/PolicyWrap.cs @@ -3,44 +3,45 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class PolicyWrap +namespace Polly.Benchmarks { - private static readonly Policy SyncPolicy = Policy.Wrap( - Policy.Handle().Retry(), - Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)), - Policy.Timeout(TimeSpan.FromMilliseconds(10)), - Policy.Bulkhead(2)); + [Config(typeof(PollyConfig))] + public class PolicyWrap + { + private static readonly Policy SyncPolicy = Policy.Wrap( + Policy.Handle().Retry(), + Policy.Handle().CircuitBreaker(2, TimeSpan.FromMinutes(1)), + Policy.Timeout(TimeSpan.FromMilliseconds(10)), + Policy.Bulkhead(2)); - private static readonly AsyncPolicy AsyncPolicy = Policy.WrapAsync( - Policy.Handle().RetryAsync(), - Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)), - Policy.TimeoutAsync(TimeSpan.FromMilliseconds(10)), - Policy.BulkheadAsync(2)); + private static readonly AsyncPolicy AsyncPolicy = Policy.WrapAsync( + Policy.Handle().RetryAsync(), + Policy.Handle().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)), + Policy.TimeoutAsync(TimeSpan.FromMilliseconds(10)), + Policy.BulkheadAsync(2)); - [Benchmark] - public void PolicyWrap_Synchronous() - { - SyncPolicy.Execute(() => Workloads.Action()); - } + [Benchmark] + public void PolicyWrap_Synchronous() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public async Task PolicyWrap_Asynchronous() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task PolicyWrap_Asynchronous() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public int PolicyWrap_Synchronous_With_Result() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public int PolicyWrap_Synchronous_With_Result() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public async Task PolicyWrap_Asynchronous_With_Result() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + [Benchmark] + public async Task PolicyWrap_Asynchronous_With_Result() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/PollyConfig.cs b/src/Polly.Benchmarks/PollyConfig.cs index 0d79053be7e..d490ae4d09c 100644 --- a/src/Polly.Benchmarks/PollyConfig.cs +++ b/src/Polly.Benchmarks/PollyConfig.cs @@ -1,36 +1,37 @@ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; -namespace Polly.Benchmarks; - -internal class PollyConfig : ManualConfig +namespace Polly.Benchmarks { - public PollyConfig() + internal class PollyConfig : ManualConfig { - var job = Job.Default; + public PollyConfig() + { + var job = Job.Default; - AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default); + AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default); - AddJob(PollyJob(job, useNuGet: true).AsBaseline()); - AddJob(PollyJob(job, useNuGet: false)); - } + AddJob(PollyJob(job, useNuGet: true).AsBaseline()); + AddJob(PollyJob(job, useNuGet: false)); + } - private static Job PollyJob(Job job, bool useNuGet) - { - var result = job - .WithId("Polly" + (useNuGet ? string.Empty : "-dev")) - .WithArguments( + private static Job PollyJob(Job job, bool useNuGet) + { + var result = job + .WithId("Polly" + (useNuGet ? string.Empty : "-dev")) + .WithArguments( new[] { new MsBuildArgument("/p:BenchmarkFromNuGet=" + useNuGet), new MsBuildArgument("/p:SignAssembly=false"), }); - if (useNuGet) - { - result = result.WithNuGet("Polly", "7.2.1"); - } + if (useNuGet) + { + result = result.WithNuGet("Polly", "7.2.1"); + } - return result; + return result; + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/Retry.cs b/src/Polly.Benchmarks/Retry.cs index def801e2455..f9ad1a5390b 100644 --- a/src/Polly.Benchmarks/Retry.cs +++ b/src/Polly.Benchmarks/Retry.cs @@ -3,77 +3,78 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class Retry +namespace Polly.Benchmarks { - private static readonly Policy SyncPolicy = Policy.Handle().Retry(); - private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().RetryAsync(); - - [Benchmark] - public void Retry_Synchronous_Succeeds() + [Config(typeof(PollyConfig))] + public class Retry { - SyncPolicy.Execute(() => Workloads.Action()); - } + private static readonly Policy SyncPolicy = Policy.Handle().Retry(); + private static readonly AsyncPolicy AsyncPolicy = Policy.Handle().RetryAsync(); - [Benchmark] - public async Task Retry_Asynchronous_Succeeds() - { - await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); - } + [Benchmark] + public void Retry_Synchronous_Succeeds() + { + SyncPolicy.Execute(() => Workloads.Action()); + } - [Benchmark] - public async Task Retry_Asynchronous_Succeeds_With_CancellationToken() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task Retry_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } - [Benchmark] - public int Retry_Synchronous_With_Result_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } + [Benchmark] + public async Task Retry_Asynchronous_Succeeds_With_CancellationToken() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } - [Benchmark] - public async Task Retry_Asynchronous_With_Result_Succeeds() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } + [Benchmark] + public int Retry_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } - [Benchmark] - public async Task Retry_Asynchronous_With_Result_Succeeds_With_CancellationToken() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } + [Benchmark] + public async Task Retry_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } - [Benchmark] - public void Retry_Synchronous_Throws_Then_Succeeds() - { - var count = 0; + [Benchmark] + public async Task Retry_Asynchronous_With_Result_Succeeds_With_CancellationToken() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } - SyncPolicy.Execute(() => + [Benchmark] + public void Retry_Synchronous_Throws_Then_Succeeds() { - if (count++ % 2 == 0) - { - throw new InvalidOperationException(); - } - }); - } + var count = 0; - [Benchmark] - public async Task Retry_Asynchronous_Throws_Then_Succeeds() - { - var count = 0; + SyncPolicy.Execute(() => + { + if (count++ % 2 == 0) + { + throw new InvalidOperationException(); + } + }); + } - await AsyncPolicy.ExecuteAsync(() => + [Benchmark] + public async Task Retry_Asynchronous_Throws_Then_Succeeds() { - if (count++ % 2 == 0) + var count = 0; + + await AsyncPolicy.ExecuteAsync(() => { - throw new InvalidOperationException(); - } + if (count++ % 2 == 0) + { + throw new InvalidOperationException(); + } - return Task.CompletedTask; - }); + return Task.CompletedTask; + }); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/Timeout.cs b/src/Polly.Benchmarks/Timeout.cs index d5b0c1542cb..74de593fad6 100644 --- a/src/Polly.Benchmarks/Timeout.cs +++ b/src/Polly.Benchmarks/Timeout.cs @@ -3,53 +3,54 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -namespace Polly.Benchmarks; - -[Config(typeof(PollyConfig))] -public class Timeout +namespace Polly.Benchmarks { - private static readonly Policy SyncPolicy = Policy.Timeout(TimeSpan.FromMilliseconds(1)); - private static readonly AsyncPolicy AsyncPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); - - [Benchmark] - public void Timeout_Synchronous_Succeeds() - { - SyncPolicy.Execute(() => Workloads.Action()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_Succeeds() - { - await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_Succeeds_With_CancellationToken() - { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); - } - - [Benchmark] - public int Timeout_Synchronous_With_Result_Succeeds() - { - return SyncPolicy.Execute(() => Workloads.Func()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_With_Result_Succeeds() - { - return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); - } - - [Benchmark] - public async Task Timeout_Asynchronous_With_Result_Succeeds_With_CancellationToken() - { - return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); - } - - [Benchmark] - public async Task Timeout_Asynchronous_Times_Out_Optimistic() + [Config(typeof(PollyConfig))] + public class Timeout { - await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionInfiniteAsync(token), CancellationToken.None); + private static readonly Policy SyncPolicy = Policy.Timeout(TimeSpan.FromMilliseconds(1)); + private static readonly AsyncPolicy AsyncPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); + + [Benchmark] + public void Timeout_Synchronous_Succeeds() + { + SyncPolicy.Execute(() => Workloads.Action()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_Succeeds() + { + await AsyncPolicy.ExecuteAsync(() => Workloads.ActionAsync()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_Succeeds_With_CancellationToken() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionAsync(token), CancellationToken.None); + } + + [Benchmark] + public int Timeout_Synchronous_With_Result_Succeeds() + { + return SyncPolicy.Execute(() => Workloads.Func()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_With_Result_Succeeds() + { + return await AsyncPolicy.ExecuteAsync(() => Workloads.FuncAsync()); + } + + [Benchmark] + public async Task Timeout_Asynchronous_With_Result_Succeeds_With_CancellationToken() + { + return await AsyncPolicy.ExecuteAsync((token) => Workloads.FuncAsync(token), CancellationToken.None); + } + + [Benchmark] + public async Task Timeout_Asynchronous_Times_Out_Optimistic() + { + await AsyncPolicy.ExecuteAsync((token) => Workloads.ActionInfiniteAsync(token), CancellationToken.None); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Benchmarks/Workloads.cs b/src/Polly.Benchmarks/Workloads.cs index fab7ecc3e1c..88de24136f3 100644 --- a/src/Polly.Benchmarks/Workloads.cs +++ b/src/Polly.Benchmarks/Workloads.cs @@ -2,49 +2,50 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Benchmarks; - -internal static class Workloads +namespace Polly.Benchmarks { - internal static void Action() + internal static class Workloads { - } + internal static void Action() + { + } - internal static Task ActionAsync() => Task.CompletedTask; + internal static Task ActionAsync() => Task.CompletedTask; - internal static Task ActionAsync(CancellationToken cancellationToken) => Task.CompletedTask; + internal static Task ActionAsync(CancellationToken cancellationToken) => Task.CompletedTask; - internal static async Task ActionInfiniteAsync() - { - while (true) + internal static async Task ActionInfiniteAsync() { - await Task.Yield(); + while (true) + { + await Task.Yield(); + } } - } - internal static async Task ActionInfiniteAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) + internal static async Task ActionInfiniteAsync(CancellationToken cancellationToken) { - await Task.Yield(); + while (!cancellationToken.IsCancellationRequested) + { + await Task.Yield(); + } } - } - internal static T Func() => default; + internal static T Func() => default; - internal static Task FuncAsync() => Task.FromResult(default); + internal static Task FuncAsync() => Task.FromResult(default); - internal static Task FuncAsync(CancellationToken cancellationToken) => Task.FromResult(default); + internal static Task FuncAsync(CancellationToken cancellationToken) => Task.FromResult(default); - internal static TResult FuncThrows() - where TException : Exception, new() - { - throw new TException(); - } + internal static TResult FuncThrows() + where TException : Exception, new() + { + throw new TException(); + } - internal static Task FuncThrowsAsync() - where TException : Exception, new() - { - throw new TException(); + internal static Task FuncThrowsAsync() + where TException : Exception, new() + { + throw new TException(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs index 45879016796..bc36194e92a 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs @@ -8,93 +8,94 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead; - -[Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] -public class BulkheadAsyncSpecs : BulkheadSpecsBase +namespace Polly.Specs.Bulkhead { - public BulkheadAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - - #region Configuration - - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + [Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] + public class BulkheadAsyncSpecs : BulkheadSpecsBase { - Action policy = () => Policy - .BulkheadAsync(0, 1); + public BulkheadAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + #region Configuration - [Fact] - public void Should_throw_when_maxQueuingActions_less_than_zero() - { - Action policy = () => Policy - .BulkheadAsync(1, -1); + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + { + Action policy = () => Policy + .BulkheadAsync(0, 1); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .BulkheadAsync(1, 0, null); + [Fact] + public void Should_throw_when_maxQueuingActions_less_than_zero() + { + Action policy = () => Policy + .BulkheadAsync(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejectedAsync"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - #endregion + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .BulkheadAsync(1, 0, null); - #region onBulkheadRejected delegate + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejectedAsync"); + } - [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + #endregion - Context contextPassedToOnRejected = null; - Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; + #region onBulkheadRejected delegate - using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) + [Fact] + public void Should_call_onBulkheadRejected_with_passed_context() { - var tcs = new TaskCompletionSource(); - using (var cancellationSource = new CancellationTokenSource()) + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnRejected = null; + Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; + + using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) { - Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; }); }); + var tcs = new TaskCompletionSource(); + using (var cancellationSource = new CancellationTokenSource()) + { + Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; }); }); - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - bulkhead.Awaiting(b => b.ExecuteAsync(_ => TaskHelper.EmptyTask, contextPassedToExecute)).Should().Throw(); + bulkhead.Awaiting(b => b.ExecuteAsync(_ => TaskHelper.EmptyTask, contextPassedToExecute)).Should().Throw(); - cancellationSource.Cancel(); - tcs.SetCanceled(); - } + cancellationSource.Cancel(); + tcs.SetCanceled(); + } - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + } } - } - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); - } + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + } - #endregion + #endregion + } } \ No newline at end of file diff --git a/src/Polly.Specs/Bulkhead/BulkheadScenario.cs b/src/Polly.Specs/Bulkhead/BulkheadScenario.cs index 64025aa39d5..1e0433afd0b 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadScenario.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadScenario.cs @@ -1,26 +1,27 @@ -namespace Polly.Specs.Bulkhead; - -internal struct BulkheadScenario +namespace Polly.Specs.Bulkhead { - readonly int _maxParallelization; - readonly int _maxQueuingActions; - readonly int _totalTestLoad; - readonly string _scenario; - readonly bool _cancelQueuing; - readonly bool _cancelExecuting; - - public BulkheadScenario(int maxParallelization, int maxQueuingActions, int totalTestLoad, bool cancelQueuing, bool cancelExecuting, string scenario) + internal struct BulkheadScenario { - _maxParallelization = maxParallelization; - _maxQueuingActions = maxQueuingActions; - _totalTestLoad = totalTestLoad; - _scenario = scenario; - _cancelQueuing = cancelQueuing; - _cancelExecuting = cancelExecuting; - } + readonly int _maxParallelization; + readonly int _maxQueuingActions; + readonly int _totalTestLoad; + readonly string _scenario; + readonly bool _cancelQueuing; + readonly bool _cancelExecuting; - public object[] ToTheoryData() - { - return new object[] {_maxParallelization, _maxQueuingActions, _totalTestLoad, _cancelQueuing, _cancelExecuting, _scenario }; + public BulkheadScenario(int maxParallelization, int maxQueuingActions, int totalTestLoad, bool cancelQueuing, bool cancelExecuting, string scenario) + { + _maxParallelization = maxParallelization; + _maxQueuingActions = maxQueuingActions; + _totalTestLoad = totalTestLoad; + _scenario = scenario; + _cancelQueuing = cancelQueuing; + _cancelExecuting = cancelExecuting; + } + + public object[] ToTheoryData() + { + return new object[] {_maxParallelization, _maxQueuingActions, _totalTestLoad, _cancelQueuing, _cancelExecuting, _scenario }; + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs b/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs index 3a334aee652..df884a239bc 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadScenarios.cs @@ -1,24 +1,25 @@ using System.Collections; using System.Collections.Generic; -namespace Polly.Specs.Bulkhead; - -/// -/// A set of test scenarios used in all BulkheadPolicy tests. -/// -internal class BulkheadScenarios : IEnumerable +namespace Polly.Specs.Bulkhead { - public IEnumerator GetEnumerator() + /// + /// A set of test scenarios used in all BulkheadPolicy tests. + /// + internal class BulkheadScenarios : IEnumerable { - yield return new BulkheadScenario(maxParallelization: 5, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, not even oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 20, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: true, scenario: "A high capacity bulkhead, with no queue, not even oversubscribed; cancel some executing.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 0, totalTestLoad: 4, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 1, totalTestLoad: 5, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); - yield return new BulkheadScenario(5, 3, 8, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); - yield return new BulkheadScenario(maxParallelization: 1, maxQueuingActions: 6, totalTestLoad: 5, cancelQueuing: true, cancelExecuting: true, scenario: "A very tight capacity bulkhead, but which allows a huge queue; enough for all actions to be gradually processed; cancel some queuing, and some executing.").ToTheoryData(); - } + public IEnumerator GetEnumerator() + { + yield return new BulkheadScenario(maxParallelization: 5, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, not even oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 20, maxQueuingActions: 0, totalTestLoad: 3, cancelQueuing: false, cancelExecuting: true, scenario: "A high capacity bulkhead, with no queue, not even oversubscribed; cancel some executing.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 0, totalTestLoad: 4, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with no queue, oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 3, maxQueuingActions: 1, totalTestLoad: 5, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with not enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); + yield return new BulkheadScenario(5, 3, 8, cancelQueuing: false, cancelExecuting: false, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 6, maxQueuingActions: 3, totalTestLoad: 9, cancelQueuing: true, cancelExecuting: true, scenario: "A bulkhead, with enough queue to avoid rejections, oversubscribed; cancel some queuing, and some executing.").ToTheoryData(); + yield return new BulkheadScenario(maxParallelization: 1, maxQueuingActions: 6, totalTestLoad: 5, cancelQueuing: true, cancelExecuting: true, scenario: "A very tight capacity bulkhead, but which allows a huge queue; enough for all actions to be gradually processed; cancel some queuing, and some executing.").ToTheoryData(); + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} \ No newline at end of file + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs index ae955c341cf..8cd0804bf39 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs @@ -6,92 +6,93 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead; - -[Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] -public class BulkheadSpecs : BulkheadSpecsBase +namespace Polly.Specs.Bulkhead { - public BulkheadSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - - #region Configuration - - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + [Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] + public class BulkheadSpecs : BulkheadSpecsBase { - Action policy = () => Policy - .Bulkhead(0, 1); + public BulkheadSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + #region Configuration - [Fact] - public void Should_throw_when_maxqueuedactions_less_than_zero() - { - Action policy = () => Policy - .Bulkhead(1, -1); + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + { + Action policy = () => Policy + .Bulkhead(0, 1); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .Bulkhead(1, 0, null); + [Fact] + public void Should_throw_when_maxqueuedactions_less_than_zero() + { + Action policy = () => Policy + .Bulkhead(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejected"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - #endregion + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .Bulkhead(1, 0, null); - #region onBulkheadRejected delegate + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejected"); + } - [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + #endregion - Context contextPassedToOnRejected = null; - Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; + #region onBulkheadRejected delegate - using (var bulkhead = Policy.Bulkhead(1, onRejected)) + [Fact] + public void Should_call_onBulkheadRejected_with_passed_context() { - var tcs = new TaskCompletionSource(); + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnRejected = null; + Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; - Task.Run(() => { bulkhead.Execute(() => { tcs.Task.Wait(); }); }); + using (var bulkhead = Policy.Bulkhead(1, onRejected)) + { + var tcs = new TaskCompletionSource(); - // Time for the other thread to kick up and take the bulkhead. - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); + Task.Run(() => { bulkhead.Execute(() => { tcs.Task.Wait(); }); }); - bulkhead.Invoking(b => b.Execute(_ => { }, contextPassedToExecute)).Should() - .Throw(); + // Time for the other thread to kick up and take the bulkhead. + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - tcs.SetCanceled(); + bulkhead.Invoking(b => b.Execute(_ => { }, contextPassedToExecute)).Should() + .Throw(); - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + tcs.SetCanceled(); + + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + } } - } - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.Bulkhead(maxParallelization, maxQueuingActions); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.Bulkhead(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); - } + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs b/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs index ea646a01526..5ac236ebbc3 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs @@ -9,385 +9,386 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead; - -[Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] -public abstract class BulkheadSpecsBase : IDisposable +namespace Polly.Specs.Bulkhead { - #region Time constraints + [Collection(Helpers.Constants.ParallelThreadDependentTestCollection)] + public abstract class BulkheadSpecsBase : IDisposable + { + #region Time constraints - protected readonly TimeSpan ShimTimeSpan = TimeSpan.FromMilliseconds(50); // How frequently to retry the assertions. - protected readonly TimeSpan CohesionTimeLimit = TimeSpan.FromMilliseconds(1000); // Consider increasing CohesionTimeLimit if bulkhead specs fail transiently in slower build environments. + protected readonly TimeSpan ShimTimeSpan = TimeSpan.FromMilliseconds(50); // How frequently to retry the assertions. + protected readonly TimeSpan CohesionTimeLimit = TimeSpan.FromMilliseconds(1000); // Consider increasing CohesionTimeLimit if bulkhead specs fail transiently in slower build environments. - #endregion - public BulkheadSpecsBase(ITestOutputHelper testOutputHelper) - { + #endregion + public BulkheadSpecsBase(ITestOutputHelper testOutputHelper) + { #if !DEBUG TestOutputHelper = new SilentOutputHelper(); #else - TestOutputHelper = new AnnotatedOutputHelper(testOutputHelper); + TestOutputHelper = new AnnotatedOutputHelper(testOutputHelper); #endif #if !NETCOREAPP1_1 - ThreadPool.SetMinThreads(50, 20); + ThreadPool.SetMinThreads(50, 20); #endif - } - - #region Operating variables - - protected IBulkheadPolicy BulkheadForStats { get; set; } + } - internal TraceableAction[] Actions { get; set; } + #region Operating variables - protected Task[] Tasks { get; set; } + protected IBulkheadPolicy BulkheadForStats { get; set; } - protected readonly AutoResetEvent StatusChangedEvent = new AutoResetEvent(false); + internal TraceableAction[] Actions { get; set; } - #endregion + protected Task[] Tasks { get; set; } - #region Scenario + protected readonly AutoResetEvent StatusChangedEvent = new AutoResetEvent(false); - protected string Scenario { get; set; } + #endregion - protected int MaxParallelization { get; set; } - protected int MaxQueuingActions { get; set; } - protected int TotalActions { get; set; } + #region Scenario - #endregion + protected string Scenario { get; set; } - #region Tracked metrics + protected int MaxParallelization { get; set; } + protected int MaxQueuingActions { get; set; } + protected int TotalActions { get; set; } - protected int ExpectedCompleted { get; set; } - protected int ExpectedCancelled { get; set; } - protected int ExpectedExecuting { get; set; } - protected int ExpectedRejects { get; set; } - protected int ExpectedQueuing { get; set; } - protected int ExpectedFaulted { get; set; } = 0; - protected int ExpectedBulkheadFree { get; set; } - protected int ExpectedQueueFree { get; set; } + #endregion - protected int ActualCompleted { get; set; } - protected int ActualCancelled { get; set; } - protected int ActualExecuting { get; set; } - protected int ActualRejects { get; set; } - protected int ActualQueuing { get; set; } - protected int ActualFaulted { get; set; } - protected int ActualBulkheadFree => BulkheadForStats.BulkheadAvailableCount; - protected int ActualQueueFree => BulkheadForStats.QueueAvailableCount; + #region Tracked metrics - #endregion + protected int ExpectedCompleted { get; set; } + protected int ExpectedCancelled { get; set; } + protected int ExpectedExecuting { get; set; } + protected int ExpectedRejects { get; set; } + protected int ExpectedQueuing { get; set; } + protected int ExpectedFaulted { get; set; } = 0; + protected int ExpectedBulkheadFree { get; set; } + protected int ExpectedQueueFree { get; set; } - #region Bulkhead behaviour + protected int ActualCompleted { get; set; } + protected int ActualCancelled { get; set; } + protected int ActualExecuting { get; set; } + protected int ActualRejects { get; set; } + protected int ActualQueuing { get; set; } + protected int ActualFaulted { get; set; } + protected int ActualBulkheadFree => BulkheadForStats.BulkheadAvailableCount; + protected int ActualQueueFree => BulkheadForStats.QueueAvailableCount; - protected abstract IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions); + #endregion - protected abstract Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action); + #region Bulkhead behaviour - [Theory, ClassData(typeof(BulkheadScenarios))] - public void Should_control_executions_per_specification(int maxParallelization, int maxQueuingActions, int totalActions, bool cancelQueuing, bool cancelExecuting, string scenario) - { - if (totalActions < 0) throw new ArgumentOutOfRangeException(nameof(totalActions)); + protected abstract IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions); - MaxParallelization = maxParallelization; - MaxQueuingActions = maxQueuingActions; - TotalActions = totalActions; - Scenario = $"MaxParallelization {maxParallelization}; MaxQueuing {maxQueuingActions}; TotalActions {totalActions}; CancelQueuing {cancelQueuing}; CancelExecuting {cancelExecuting}: {scenario}"; + protected abstract Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action); - var bulkhead = GetBulkhead(maxParallelization, maxQueuingActions); - using (bulkhead) + [Theory, ClassData(typeof(BulkheadScenarios))] + public void Should_control_executions_per_specification(int maxParallelization, int maxQueuingActions, int totalActions, bool cancelQueuing, bool cancelExecuting, string scenario) { - BulkheadForStats = bulkhead; + if (totalActions < 0) throw new ArgumentOutOfRangeException(nameof(totalActions)); - // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). - Actions = new TraceableAction[totalActions]; - for (var i = 0; i < totalActions; i++) { Actions[i] = new TraceableAction(i, StatusChangedEvent, TestOutputHelper); } + MaxParallelization = maxParallelization; + MaxQueuingActions = maxQueuingActions; + TotalActions = totalActions; + Scenario = $"MaxParallelization {maxParallelization}; MaxQueuing {maxQueuingActions}; TotalActions {totalActions}; CancelQueuing {cancelQueuing}; CancelExecuting {cancelExecuting}: {scenario}"; - // Throw all the delegates at the bulkhead simultaneously. - Tasks = new Task[totalActions]; - for (var i = 0; i < totalActions; i++) { Tasks[i] = ExecuteOnBulkhead(bulkhead, Actions[i]); } - - OutputStatus("Immediately after queueing..."); - - // Assert the expected distributions of executing, queuing, rejected and completed - when all delegates thrown at bulkhead. - ExpectedCompleted = 0; - ExpectedCancelled = 0; - ExpectedExecuting = Math.Min(totalActions, maxParallelization); - ExpectedRejects = Math.Max(0, totalActions - maxParallelization - maxQueuingActions); - ExpectedQueuing = Math.Min(maxQueuingActions, Math.Max(0, totalActions - maxParallelization)); - ExpectedBulkheadFree = maxParallelization - ExpectedExecuting; - ExpectedQueueFree = maxQueuingActions - ExpectedQueuing; - - try - { - Within(CohesionTimeLimit, ActualsMatchExpecteds); - } - finally + var bulkhead = GetBulkhead(maxParallelization, maxQueuingActions); + using (bulkhead) { - OutputStatus("Expected initial state verified..."); - } + BulkheadForStats = bulkhead; - // Complete or cancel delegates one by one, and expect others to take their place (if a slot released and others remain queueing); until all work is done. - while (ExpectedExecuting > 0) - { - if (cancelQueuing) - { - TestOutputHelper.WriteLine("Cancelling a queueing task..."); + // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). + Actions = new TraceableAction[totalActions]; + for (var i = 0; i < totalActions; i++) { Actions[i] = new TraceableAction(i, StatusChangedEvent, TestOutputHelper); } + + // Throw all the delegates at the bulkhead simultaneously. + Tasks = new Task[totalActions]; + for (var i = 0; i < totalActions; i++) { Tasks[i] = ExecuteOnBulkhead(bulkhead, Actions[i]); } - Actions.First(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Cancel(); + OutputStatus("Immediately after queueing..."); - ExpectedCancelled++; - ExpectedQueuing--; - ExpectedQueueFree++; + // Assert the expected distributions of executing, queuing, rejected and completed - when all delegates thrown at bulkhead. + ExpectedCompleted = 0; + ExpectedCancelled = 0; + ExpectedExecuting = Math.Min(totalActions, maxParallelization); + ExpectedRejects = Math.Max(0, totalActions - maxParallelization - maxQueuingActions); + ExpectedQueuing = Math.Min(maxQueuingActions, Math.Max(0, totalActions - maxParallelization)); + ExpectedBulkheadFree = maxParallelization - ExpectedExecuting; + ExpectedQueueFree = maxQueuingActions - ExpectedQueuing; - cancelQueuing = false; + try + { + Within(CohesionTimeLimit, ActualsMatchExpecteds); } - else if (cancelExecuting) + finally { - TestOutputHelper.WriteLine("Cancelling an executing task..."); - - Actions.First(a => a.Status == TraceableActionStatus.Executing).Cancel(); + OutputStatus("Expected initial state verified..."); + } - ExpectedCancelled++; - if (ExpectedQueuing > 0) + // Complete or cancel delegates one by one, and expect others to take their place (if a slot released and others remain queueing); until all work is done. + while (ExpectedExecuting > 0) + { + if (cancelQueuing) { + TestOutputHelper.WriteLine("Cancelling a queueing task..."); + + Actions.First(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Cancel(); + + ExpectedCancelled++; ExpectedQueuing--; ExpectedQueueFree++; + + cancelQueuing = false; } - else + else if (cancelExecuting) { - ExpectedExecuting--; - ExpectedBulkheadFree++; + TestOutputHelper.WriteLine("Cancelling an executing task..."); + + Actions.First(a => a.Status == TraceableActionStatus.Executing).Cancel(); + + ExpectedCancelled++; + if (ExpectedQueuing > 0) + { + ExpectedQueuing--; + ExpectedQueueFree++; + } + else + { + ExpectedExecuting--; + ExpectedBulkheadFree++; + } + + cancelExecuting = false; } + else // Complete an executing delegate. + { + TestOutputHelper.WriteLine("Completing a task..."); - cancelExecuting = false; - } - else // Complete an executing delegate. - { - TestOutputHelper.WriteLine("Completing a task..."); + Actions.First(a => a.Status == TraceableActionStatus.Executing).AllowCompletion(); - Actions.First(a => a.Status == TraceableActionStatus.Executing).AllowCompletion(); + ExpectedCompleted++; - ExpectedCompleted++; + if (ExpectedQueuing > 0) + { + ExpectedQueuing--; + ExpectedQueueFree++; + } + else + { + ExpectedExecuting--; + ExpectedBulkheadFree++; + } - if (ExpectedQueuing > 0) + } + + try { - ExpectedQueuing--; - ExpectedQueueFree++; + Within(CohesionTimeLimit, ActualsMatchExpecteds); } - else + finally { - ExpectedExecuting--; - ExpectedBulkheadFree++; + OutputStatus("End of next loop iteration..."); } } - try - { - Within(CohesionTimeLimit, ActualsMatchExpecteds); - } - finally - { - OutputStatus("End of next loop iteration..."); - } + EnsureNoUnbservedTaskExceptions(); + TestOutputHelper.WriteLine("Verifying all tasks completed..."); + Within(CohesionTimeLimit, AllTasksCompleted); } + } - EnsureNoUnbservedTaskExceptions(); + protected void UpdateActuals() + { + ActualCompleted = ActualCancelled = ActualExecuting = ActualRejects = ActualQueuing = ActualFaulted = 0; - TestOutputHelper.WriteLine("Verifying all tasks completed..."); - Within(CohesionTimeLimit, AllTasksCompleted); + foreach (var action in Actions) + { + switch (action.Status) + { + case TraceableActionStatus.Canceled: + ActualCancelled++; + break; + case TraceableActionStatus.Completed: + ActualCompleted++; + break; + case TraceableActionStatus.Executing: + ActualExecuting++; + break; + case TraceableActionStatus.Faulted: + ActualFaulted++; + break; + case TraceableActionStatus.QueueingForSemaphore: + ActualQueuing++; + break; + case TraceableActionStatus.Rejected: + ActualRejects++; + break; + case TraceableActionStatus.StartRequested: + case TraceableActionStatus.Unstarted: + // We do not care to count these. + break; + default: + throw new InvalidOperationException($"Unaccounted for {nameof(TraceableActionStatus)}: {action.Status}."); + } + } } - } - protected void UpdateActuals() - { - ActualCompleted = ActualCancelled = ActualExecuting = ActualRejects = ActualQueuing = ActualFaulted = 0; - - foreach (var action in Actions) + protected AssertionFailure ActualsMatchExpecteds() { - switch (action.Status) + UpdateActuals(); + + if (ExpectedFaulted != ActualFaulted) { - case TraceableActionStatus.Canceled: - ActualCancelled++; - break; - case TraceableActionStatus.Completed: - ActualCompleted++; - break; - case TraceableActionStatus.Executing: - ActualExecuting++; - break; - case TraceableActionStatus.Faulted: - ActualFaulted++; - break; - case TraceableActionStatus.QueueingForSemaphore: - ActualQueuing++; - break; - case TraceableActionStatus.Rejected: - ActualRejects++; - break; - case TraceableActionStatus.StartRequested: - case TraceableActionStatus.Unstarted: - // We do not care to count these. - break; - default: - throw new InvalidOperationException($"Unaccounted for {nameof(TraceableActionStatus)}: {action.Status}."); + return new AssertionFailure(ExpectedFaulted, ActualFaulted, nameof(ExpectedFaulted)); } - } - } - protected AssertionFailure ActualsMatchExpecteds() - { - UpdateActuals(); + if (ExpectedRejects != ActualRejects) + { + return new AssertionFailure(ExpectedRejects, ActualRejects, nameof(ExpectedRejects)); + } - if (ExpectedFaulted != ActualFaulted) - { - return new AssertionFailure(ExpectedFaulted, ActualFaulted, nameof(ExpectedFaulted)); - } + if (ExpectedCancelled != ActualCancelled) + { + return new AssertionFailure(ExpectedCancelled, ActualCancelled, nameof(ExpectedCancelled)); + } - if (ExpectedRejects != ActualRejects) - { - return new AssertionFailure(ExpectedRejects, ActualRejects, nameof(ExpectedRejects)); - } + if (ExpectedCompleted != ActualCompleted) + { + return new AssertionFailure(ExpectedCompleted, ActualCompleted, nameof(ExpectedCompleted)); + } - if (ExpectedCancelled != ActualCancelled) - { - return new AssertionFailure(ExpectedCancelled, ActualCancelled, nameof(ExpectedCancelled)); - } + if (ExpectedExecuting != ActualExecuting) + { + return new AssertionFailure(ExpectedExecuting, ActualExecuting, nameof(ExpectedExecuting)); + } - if (ExpectedCompleted != ActualCompleted) - { - return new AssertionFailure(ExpectedCompleted, ActualCompleted, nameof(ExpectedCompleted)); - } + if (ExpectedQueuing != ActualQueuing) + { + return new AssertionFailure(ExpectedQueuing, ActualQueuing, nameof(ExpectedQueuing)); + } - if (ExpectedExecuting != ActualExecuting) - { - return new AssertionFailure(ExpectedExecuting, ActualExecuting, nameof(ExpectedExecuting)); - } + if (ExpectedBulkheadFree != ActualBulkheadFree) + { + return new AssertionFailure(ExpectedBulkheadFree, ActualBulkheadFree, nameof(ExpectedBulkheadFree)); + } - if (ExpectedQueuing != ActualQueuing) - { - return new AssertionFailure(ExpectedQueuing, ActualQueuing, nameof(ExpectedQueuing)); + if (ExpectedQueueFree != ActualQueueFree) + { + return new AssertionFailure(ExpectedQueueFree, ActualQueueFree, nameof(ExpectedQueueFree)); + } + + return null; } - if (ExpectedBulkheadFree != ActualBulkheadFree) + protected AssertionFailure AllTasksCompleted() { - return new AssertionFailure(ExpectedBulkheadFree, ActualBulkheadFree, nameof(ExpectedBulkheadFree)); + var countTasksCompleted = Tasks.Count(t => t.IsCompleted); + if (countTasksCompleted < TotalActions) + { + return new AssertionFailure(TotalActions, countTasksCompleted, nameof(countTasksCompleted)); + } + + return null; } - if (ExpectedQueueFree != ActualQueueFree) + protected void EnsureNoUnbservedTaskExceptions() { - return new AssertionFailure(ExpectedQueueFree, ActualQueueFree, nameof(ExpectedQueueFree)); + for (var i = 0; i < Tasks.Length; i++) + { + try + { + Tasks[i].Wait(); + } + catch (Exception e) + { + throw new Exception("Task " + i + " raised the following unobserved task exception: ", e); + } + } } - return null; - } + #endregion - protected AssertionFailure AllTasksCompleted() - { - var countTasksCompleted = Tasks.Count(t => t.IsCompleted); - if (countTasksCompleted < TotalActions) + protected AssertionFailure Expect(int expected, Func actualFunc, string measure) { - return new AssertionFailure(TotalActions, countTasksCompleted, nameof(countTasksCompleted)); + var actual = actualFunc(); + return actual != expected ? new AssertionFailure(expected, actual, measure) : null; } - return null; - } - - protected void EnsureNoUnbservedTaskExceptions() - { - for (var i = 0; i < Tasks.Length; i++) + protected void Within(TimeSpan timeSpan, Func actionContainingAssertions) { - try - { - Tasks[i].Wait(); - } - catch (Exception e) + var permitted = timeSpan; + var watch = Stopwatch.StartNew(); + while (true) { - throw new Exception("Task " + i + " raised the following unobserved task exception: ", e); + var potentialFailure = actionContainingAssertions(); + if (potentialFailure == null) + { + break; + } + + if (watch.Elapsed > permitted) + { + TestOutputHelper.WriteLine("Failing assertion on: {0}", potentialFailure.Measure); + potentialFailure.Actual.Should().Be(potentialFailure.Expected, $"for '{potentialFailure.Measure}', in scenario: {Scenario}"); + throw new InvalidOperationException("Code should never reach here. Preceding assertion should fail."); + } + + var signaled = StatusChangedEvent.WaitOne(ShimTimeSpan); + if (signaled) + { + // Following TraceableAction.CaptureCompletion() signalling the AutoResetEvent, + // there can be race conditions between on the one hand exiting the bulkhead semaphore (and potentially another execution gaining it), + // and the assertion being verified here about those same facts. + // If that race is lost by the real-world state change, and the AutoResetEvent signal occurred very close to timeoutTime, + // there might not be a second chance. + // We therefore permit another shim time for the condition to come good. + permitted += CohesionTimeLimit; + } } } - } - #endregion + #region Output helpers - protected AssertionFailure Expect(int expected, Func actualFunc, string measure) - { - var actual = actualFunc(); - return actual != expected ? new AssertionFailure(expected, actual, measure) : null; - } + protected readonly ITestOutputHelper TestOutputHelper; - protected void Within(TimeSpan timeSpan, Func actionContainingAssertions) - { - var permitted = timeSpan; - var watch = Stopwatch.StartNew(); - while (true) + protected void OutputStatus(string statusHeading) { - var potentialFailure = actionContainingAssertions(); - if (potentialFailure == null) - { - break; - } - - if (watch.Elapsed > permitted) - { - TestOutputHelper.WriteLine("Failing assertion on: {0}", potentialFailure.Measure); - potentialFailure.Actual.Should().Be(potentialFailure.Expected, $"for '{potentialFailure.Measure}', in scenario: {Scenario}"); - throw new InvalidOperationException("Code should never reach here. Preceding assertion should fail."); - } + TestOutputHelper.WriteLine(statusHeading); + TestOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", ActualBulkheadFree, MaxParallelization); + TestOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", ActualQueueFree, MaxQueuingActions); - var signaled = StatusChangedEvent.WaitOne(ShimTimeSpan); - if (signaled) + for (var i = 0; i < Actions.Length; i++) { - // Following TraceableAction.CaptureCompletion() signalling the AutoResetEvent, - // there can be race conditions between on the one hand exiting the bulkhead semaphore (and potentially another execution gaining it), - // and the assertion being verified here about those same facts. - // If that race is lost by the real-world state change, and the AutoResetEvent signal occurred very close to timeoutTime, - // there might not be a second chance. - // We therefore permit another shim time for the condition to come good. - permitted += CohesionTimeLimit; + TestOutputHelper.WriteLine("Action {0}: {1}", i, Actions[i].Status); } + TestOutputHelper.WriteLine(String.Empty); } - } - - #region Output helpers - - protected readonly ITestOutputHelper TestOutputHelper; - - protected void OutputStatus(string statusHeading) - { - TestOutputHelper.WriteLine(statusHeading); - TestOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", ActualBulkheadFree, MaxParallelization); - TestOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", ActualQueueFree, MaxQueuingActions); - for (var i = 0; i < Actions.Length; i++) + private void ShowTestOutput() { - TestOutputHelper.WriteLine("Action {0}: {1}", i, Actions[i].Status); + ((AnnotatedOutputHelper) TestOutputHelper).Flush(); } - TestOutputHelper.WriteLine(String.Empty); - } - - private void ShowTestOutput() - { - ((AnnotatedOutputHelper) TestOutputHelper).Flush(); - } - #endregion + #endregion - public void Dispose() - { + public void Dispose() + { #if DEBUG - ShowTestOutput(); + ShowTestOutput(); #endif - if (Actions != null) - { - foreach (var action in Actions) + if (Actions != null) { - action.Dispose(); + foreach (var action in Actions) + { + action.Dispose(); + } } - } - StatusChangedEvent.Dispose(); + StatusChangedEvent.Dispose(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs index 641ad4a7911..0a03318dce9 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs @@ -9,101 +9,102 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead; - -[Collection(Constants.ParallelThreadDependentTestCollection)] -public class BulkheadTResultAsyncSpecs : BulkheadSpecsBase +namespace Polly.Specs.Bulkhead { - public BulkheadTResultAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - - #region Configuration - - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + [Collection(Constants.ParallelThreadDependentTestCollection)] + public class BulkheadTResultAsyncSpecs : BulkheadSpecsBase { - Action policy = () => Policy - .BulkheadAsync(0, 1); + public BulkheadTResultAsyncSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + #region Configuration - [Fact] - public void Should_throw_when_maxQueuingActions_less_than_zero() - { - Action policy = () => Policy - .BulkheadAsync(1, -1); + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + { + Action policy = () => Policy + .BulkheadAsync(0, 1); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .BulkheadAsync(1, 0, null); + [Fact] + public void Should_throw_when_maxQueuingActions_less_than_zero() + { + Action policy = () => Policy + .BulkheadAsync(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejectedAsync"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - #endregion + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .BulkheadAsync(1, 0, null); - #region onBulkheadRejected delegate + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejectedAsync"); + } - [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnRejected = null; - Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; - - using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) - { - var tcs = new TaskCompletionSource(); - using (var cancellationSource = new CancellationTokenSource()) - { - Task.Run(() => { - bulkhead.ExecuteAsync(async () => - { - await tcs.Task; - return 0; + #endregion + + #region onBulkheadRejected delegate + + [Fact] + public void Should_call_onBulkheadRejected_with_passed_context() + { + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnRejected = null; + Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; + + using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) + { + var tcs = new TaskCompletionSource(); + using (var cancellationSource = new CancellationTokenSource()) + { + Task.Run(() => { + bulkhead.ExecuteAsync(async () => + { + await tcs.Task; + return 0; + }); }); - }); - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - bulkhead.Awaiting(b => b.ExecuteAsync(_ => Task.FromResult(1), contextPassedToExecute)).Should().Throw(); + bulkhead.Awaiting(b => b.ExecuteAsync(_ => Task.FromResult(1), contextPassedToExecute)).Should().Throw(); - cancellationSource.Cancel(); - tcs.SetCanceled(); - } + cancellationSource.Cancel(); + tcs.SetCanceled(); + } - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + } } - } - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.BulkheadAsync(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); - } + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkheadAsync((AsyncBulkheadPolicy)bulkhead); + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs index fa2ce2d6527..99630888728 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs @@ -9,98 +9,99 @@ using Xunit; using Xunit.Abstractions; -namespace Polly.Specs.Bulkhead; - -[Collection(Constants.ParallelThreadDependentTestCollection)] -public class BulkheadTResultSpecs : BulkheadSpecsBase +namespace Polly.Specs.Bulkhead { - public BulkheadTResultSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - - #region Configuration - - [Fact] - public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + [Collection(Constants.ParallelThreadDependentTestCollection)] + public class BulkheadTResultSpecs : BulkheadSpecsBase { - Action policy = () => Policy - .Bulkhead(0, 1); + public BulkheadTResultSpecs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - policy.Should().Throw().And - .ParamName.Should().Be("maxParallelization"); - } + #region Configuration - [Fact] - public void Should_throw_when_maxQueuingActions_less_than_zero() - { - Action policy = () => Policy - .Bulkhead(1, -1); + [Fact] + public void Should_throw_when_maxparallelization_less_or_equal_to_zero() + { + Action policy = () => Policy + .Bulkhead(0, 1); - policy.Should().Throw().And - .ParamName.Should().Be("maxQueuingActions"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxParallelization"); + } - [Fact] - public void Should_throw_when_onBulkheadRejected_is_null() - { - Action policy = () => Policy - .Bulkhead(1, 0, null); + [Fact] + public void Should_throw_when_maxQueuingActions_less_than_zero() + { + Action policy = () => Policy + .Bulkhead(1, -1); - policy.Should().Throw().And - .ParamName.Should().Be("onBulkheadRejected"); - } + policy.Should().Throw().And + .ParamName.Should().Be("maxQueuingActions"); + } - #endregion + [Fact] + public void Should_throw_when_onBulkheadRejected_is_null() + { + Action policy = () => Policy + .Bulkhead(1, 0, null); - #region onBulkheadRejected delegate + policy.Should().Throw().And + .ParamName.Should().Be("onBulkheadRejected"); + } - [Fact] - public void Should_call_onBulkheadRejected_with_passed_context() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnRejected = null; - Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; - - using (var bulkhead = Policy.Bulkhead(1, onRejected)) - { - var tcs = new TaskCompletionSource(); - using (var cancellationSource = new CancellationTokenSource()) - { - Task.Run(() => { - bulkhead.Execute(() => - { - tcs.Task.Wait(); - return 0; + #endregion + + #region onBulkheadRejected delegate + + [Fact] + public void Should_call_onBulkheadRejected_with_passed_context() + { + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnRejected = null; + Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; + + using (var bulkhead = Policy.Bulkhead(1, onRejected)) + { + var tcs = new TaskCompletionSource(); + using (var cancellationSource = new CancellationTokenSource()) + { + Task.Run(() => { + bulkhead.Execute(() => + { + tcs.Task.Wait(); + return 0; + }); }); - }); - Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); + Within(CohesionTimeLimit, () => Expect(0, () => bulkhead.BulkheadAvailableCount, nameof(bulkhead.BulkheadAvailableCount))); - bulkhead.Invoking(b => b.Execute(_ => 1, contextPassedToExecute)).Should().Throw(); + bulkhead.Invoking(b => b.Execute(_ => 1, contextPassedToExecute)).Should().Throw(); - cancellationSource.Cancel(); - tcs.SetCanceled(); - } + cancellationSource.Cancel(); + tcs.SetCanceled(); + } - contextPassedToOnRejected.Should().NotBeNull(); - contextPassedToOnRejected.OperationKey.Should().Be(operationKey); - contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + contextPassedToOnRejected.Should().NotBeNull(); + contextPassedToOnRejected.OperationKey.Should().Be(operationKey); + contextPassedToOnRejected.Should().BeSameAs(contextPassedToExecute); + } } - } - #endregion + #endregion - #region Bulkhead behaviour + #region Bulkhead behaviour - protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) - { - return Policy.Bulkhead(maxParallelization, maxQueuingActions); - } + protected override IBulkheadPolicy GetBulkhead(int maxParallelization, int maxQueuingActions) + { + return Policy.Bulkhead(maxParallelization, maxQueuingActions); + } - protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) - { - return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); - } + protected override Task ExecuteOnBulkhead(IBulkheadPolicy bulkhead, TraceableAction action) + { + return action.ExecuteOnBulkhead((BulkheadPolicy) bulkhead); + } - #endregion -} \ No newline at end of file + #endregion + } +} diff --git a/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs b/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs index 193bcae6aa7..471df56b9f1 100644 --- a/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs +++ b/src/Polly.Specs/Bulkhead/IBulkheadPolicySpecs.cs @@ -2,23 +2,24 @@ using Polly.Bulkhead; using Xunit; -namespace Polly.Specs.Bulkhead; - -public class IBulkheadPolicySpecs +namespace Polly.Specs.Bulkhead { - [Fact] - public void Should_be_able_to_use_BulkheadAvailableCount_via_interface() + public class IBulkheadPolicySpecs { - IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); + [Fact] + public void Should_be_able_to_use_BulkheadAvailableCount_via_interface() + { + IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); - bulkhead.BulkheadAvailableCount.Should().Be(20); - } + bulkhead.BulkheadAvailableCount.Should().Be(20); + } - [Fact] - public void Should_be_able_to_use_QueueAvailableCount_via_interface() - { - IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); + [Fact] + public void Should_be_able_to_use_QueueAvailableCount_via_interface() + { + IBulkheadPolicy bulkhead = Policy.Bulkhead(20, 10); - bulkhead.QueueAvailableCount.Should().Be(10); + bulkhead.QueueAvailableCount.Should().Be(10); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs b/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs index 090aba34c1d..0dbcd73ac2d 100644 --- a/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs +++ b/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs @@ -5,57 +5,58 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class AbsoluteTtlSpecs : IDisposable +namespace Polly.Specs.Caching { - [Fact] - public void Should_be_able_to_configure_for_near_future_time() - { - Action configure = () => new AbsoluteTtl(DateTime.Today.AddDays(1)); - - configure.Should().NotThrow(); - } - - [Fact] - public void Should_be_able_to_configure_for_far_future() - { - Action configure = () => new AbsoluteTtl(DateTimeOffset.MaxValue); - - configure.Should().NotThrow(); - } - - [Fact] - public void Should_be_able_to_configure_for_past() - { - Action configure = () => new AbsoluteTtl(DateTimeOffset.MinValue); - - configure.Should().NotThrow(); - } - - [Fact] - public void Should_return_zero_ttl_if_configured_to_expire_in_past() - { - var ttlStrategy = new AbsoluteTtl(SystemClock.DateTimeOffsetUtcNow().Subtract(TimeSpan.FromTicks(1))); - - ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); - } - - [Fact] - public void Should_return_timespan_reflecting_time_until_expiry() - { - var today = DateTime.Today; - var tomorrow = today.AddDays(1); - - var ttlStrategy = new AbsoluteTtl(tomorrow); - - SystemClock.DateTimeOffsetUtcNow = () => today; - ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.FromDays(1)); - } - - public void Dispose() + [Collection(Constants.SystemClockDependentTestCollection)] + public class AbsoluteTtlSpecs : IDisposable { - SystemClock.Reset(); + [Fact] + public void Should_be_able_to_configure_for_near_future_time() + { + Action configure = () => new AbsoluteTtl(DateTime.Today.AddDays(1)); + + configure.Should().NotThrow(); + } + + [Fact] + public void Should_be_able_to_configure_for_far_future() + { + Action configure = () => new AbsoluteTtl(DateTimeOffset.MaxValue); + + configure.Should().NotThrow(); + } + + [Fact] + public void Should_be_able_to_configure_for_past() + { + Action configure = () => new AbsoluteTtl(DateTimeOffset.MinValue); + + configure.Should().NotThrow(); + } + + [Fact] + public void Should_return_zero_ttl_if_configured_to_expire_in_past() + { + var ttlStrategy = new AbsoluteTtl(SystemClock.DateTimeOffsetUtcNow().Subtract(TimeSpan.FromTicks(1))); + + ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); + } + + [Fact] + public void Should_return_timespan_reflecting_time_until_expiry() + { + var today = DateTime.Today; + var tomorrow = today.AddDays(1); + + var ttlStrategy = new AbsoluteTtl(tomorrow); + + SystemClock.DateTimeOffsetUtcNow = () => today; + ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.FromDays(1)); + } + + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs index 6982fb80f77..43651f19b69 100644 --- a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs @@ -8,702 +8,703 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CacheAsyncSpecs : IDisposable +namespace Polly.Specs.Caching { - #region Configuration - - [Fact] - public void Should_throw_when_cache_provider_is_null() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CacheAsyncSpecs : IDisposable { - IAsyncCacheProvider cacheProvider = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } + #region Configuration - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } + [Fact] + public void Should_throw_when_cache_provider_is_null() + { + IAsyncCacheProvider cacheProvider = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } + + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } - #endregion + #endregion - #region Caching behaviours + #region Caching behaviours - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await cache.ExecuteAsync(async _ => + (await cache.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); - var cache = Policy.CacheAsync(stubCacheProvider, ttl); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var ttl = TimeSpan.FromMinutes(30); + var cache = Policy.CacheAsync(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - var delegateInvocations = 0; - Func> func = async _ => + var delegateInvocations = 0; + Func> func = async _ => + { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; + + var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; + + // First execution should execute delegate and put result in the cache. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); + + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } + + [Fact] + public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; - - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - - // First execution should execute delegate and put result in the cache. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - [Fact] - public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } - - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - Func> func = async _ => - { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + var delegateInvocations = 0; + Func> func = async _ => + { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - [Fact] - public async Task Should_allow_custom_FuncCacheKeyStrategy() - { - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + [Fact] + public async Task Should_allow_custom_FuncCacheKeyStrategy() + { + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - var person1 = new object(); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var person2 = new object(); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + var person1 = new object(); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + var person2 = new object(); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; + var funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + [Fact] + public async Task Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - var person1 = new object(); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var person2 = new object(); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + var person1 = new object(); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + var person2 = new object(); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; + var funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Caching behaviours, default(TResult) + #region Caching behaviours, default(TResult) - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + (await cache.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() - { - ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + var valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + (await cache.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, new Context(operationKey))) + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Non-generic CachePolicy in non-generic PolicyWrap + #region Non-generic CachePolicy in non-generic PolicyWrap - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(cache, noop); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(cache, noop); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await wrap.ExecuteAsync(async _ => + (await wrap.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(noop, cache); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(noop, cache); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await wrap.ExecuteAsync(async _ => + (await wrap.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(noop, cache, noop); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(noop, cache, noop); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await wrap.ExecuteAsync(async _ => + (await wrap.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region No-op pass-through behaviour + #region No-op pass-through behaviour - [Fact] - public async Task Should_always_execute_delegate_if_execution_key_not_set() - { - var valueToReturn = Guid.NewGuid().ToString(); + [Fact] + public async Task Should_always_execute_delegate_if_execution_key_not_set() + { + var valueToReturn = Guid.NewGuid().ToString(); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - var func = async () => { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + var delegateInvocations = 0; + var func = async () => { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - [Fact] - public void Should_always_execute_delegate_if_execution_is_void_returning() - { - var operationKey = "SomeKey"; + [Fact] + public void Should_always_execute_delegate_if_execution_is_void_returning() + { + var operationKey = "SomeKey"; - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - Func action = async _ => { delegateInvocations++; await TaskHelper.EmptyTask; }; + var delegateInvocations = 0; + Func action = async _ => { delegateInvocations++; await TaskHelper.EmptyTask; }; - cache.ExecuteAsync(action, new Context(operationKey)); - delegateInvocations.Should().Be(1); + cache.ExecuteAsync(action, new Context(operationKey)); + delegateInvocations.Should().Be(1); - cache.ExecuteAsync(action, new Context(operationKey)); - delegateInvocations.Should().Be(2); - } + cache.ExecuteAsync(action, new Context(operationKey)); + delegateInvocations.Should().Be(2); + } - #endregion + #endregion - #region Cancellation + #region Cancellation - [Fact] - public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + var tokenSource = new CancellationTokenSource(); - var delegateInvocations = 0; - Func> func = async (_, _) => - { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + var delegateInvocations = 0; + Func> func = async (_, _) => + { + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - tokenSource.Cancel(); + tokenSource.Cancel(); - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - delegateInvocations.Should().Be(1); - } + cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + delegateInvocations.Should().Be(1); + } - [Fact] - public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + var tokenSource = new CancellationTokenSource(); - Func> func = async (_, ct) => - { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - await TaskHelper.EmptyTask; - return valueToReturn; - }; - - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + Func> func = async (_, ct) => + { + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + await TaskHelper.EmptyTask; + return valueToReturn; + }; - #endregion + cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); - #region Policy hooks + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } - [Fact] - public async Task Should_call_onError_delegate_if_cache_get_errors() - { - var ex = new Exception(); - IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); + #endregion + + #region Policy hooks - Exception exceptionFromCacheProvider = null; + [Fact] + public async Task Should_call_onError_delegate_if_cache_get_errors() + { + var ex = new Exception(); + IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); + + Exception exceptionFromCacheProvider = null; - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - // Even though value is in cache, get will error; so value is returned from execution. - (await cache.ExecuteAsync(async _ => + // Even though value is in cache, get will error; so value is returned from execution. + (await cache.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromExecution); - delegateExecuted.Should().BeTrue(); - - // And error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); - } - - [Fact] - public async Task Should_call_onError_delegate_if_cache_put_errors() - { - var ex = new Exception(); - IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); - - Exception exceptionFromCacheProvider = null; - - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + .Should().Be(valueToReturnFromExecution); + delegateExecuted.Should().BeTrue(); - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + // And error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); + } - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + [Fact] + public async Task Should_call_onError_delegate_if_cache_put_errors() + { + var ex = new Exception(); + IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + Exception exceptionFromCacheProvider = null; - // error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - // failed to put it in the cache - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - [Fact] - public async Task Should_execute_oncacheget_after_got_from_cache() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - const string operationKey = "SomeOperationKey"; - string keyPassedToDelegate = null; + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - var contextToExecute = new Context(operationKey); - Context contextPassedToDelegate = null; + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; + // error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + // failed to put it in the cache + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - var delegateExecuted = false; - (await cache.ExecuteAsync(async _ => - { - delegateExecuted = true; - await TaskHelper.EmptyTask; - return valueToReturnFromExecution; - }, contextToExecute)) - .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - - contextPassedToDelegate.Should().BeSameAs(contextToExecute); - keyPassedToDelegate.Should().Be(operationKey); - } - - [Fact] - public async Task Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() - { - const string valueToReturn = "valueToReturn"; + [Fact] + public async Task Should_execute_oncacheget_after_got_from_cache() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + + const string operationKey = "SomeOperationKey"; + string keyPassedToDelegate = null; + + var contextToExecute = new Context(operationKey); + Context contextPassedToDelegate = null; + + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + + var delegateExecuted = false; + (await cache.ExecuteAsync(async _ => + { + delegateExecuted = true; + await TaskHelper.EmptyTask; + return valueToReturnFromExecution; + }, contextToExecute)) + .Should().Be(valueToReturnFromCache); + delegateExecuted.Should().BeFalse(); + + contextPassedToDelegate.Should().BeSameAs(contextToExecute); + keyPassedToDelegate.Should().Be(operationKey); + } + + [Fact] + public async Task Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() + { + const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + var contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); - keyPassedToOnCachePut.Should().Be(operationKey); - } + contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); + keyPassedToOnCachePut.Should().Be(operationKey); + } - [Fact] - public async Task Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; + [Fact] + public async Task Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + var contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); - contextPassedToOnCachePut.Should().BeNull(); - keyPassedToOnCachePut.Should().BeNull(); - } + contextPassedToOnCachePut.Should().BeNull(); + keyPassedToOnCachePut.Should().BeNull(); + } - [Fact] - public async Task Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() - { - var valueToReturn = Guid.NewGuid().ToString(); + [Fact] + public async Task Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() + { + var valueToReturn = Guid.NewGuid().ToString(); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - var onCacheMissExecuted = false; - Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; + var onCacheMissExecuted = false; + Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; - var cache = Policy.CacheAsync(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); + var cache = Policy.CacheAsync(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); - (await cache.ExecuteAsync(async () => + (await cache.ExecuteAsync(async () => { await TaskHelper.EmptyTask; return valueToReturn; } /*, no operation key */)) .Should().Be(valueToReturn); - onCacheMissExecuted.Should().BeFalse(); - } + onCacheMissExecuted.Should().BeFalse(); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/CacheSpecs.cs b/src/Polly.Specs/Caching/CacheSpecs.cs index 8e81b9cafa1..f58fc012e86 100644 --- a/src/Polly.Specs/Caching/CacheSpecs.cs +++ b/src/Polly.Specs/Caching/CacheSpecs.cs @@ -8,692 +8,693 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Caching; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CacheSpecs : IDisposable +namespace Polly.Specs.Caching { - #region Configuration - - [Fact] - public void Should_throw_when_cache_provider_is_null() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CacheSpecs : IDisposable { - ISyncCacheProvider cacheProvider = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } + #region Configuration - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.Cache(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } + [Fact] + public void Should_throw_when_cache_provider_is_null() + { + ISyncCacheProvider cacheProvider = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.Cache(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } + + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } - #endregion + #endregion - #region Caching behaviours + #region Caching behaviours - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - cache.Execute(_ => + cache.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); - var cache = Policy.Cache(stubCacheProvider, ttl); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var ttl = TimeSpan.FromMinutes(30); + var cache = Policy.Cache(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - var delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; + var delegateInvocations = 0; + Func func = _ => + { + delegateInvocations++; + return valueToReturn; + }; - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; + var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - // First execution should execute delegate and put result in the cache. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + // First execution should execute delegate and put result in the cache. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - [Fact] - public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; + var delegateInvocations = 0; + Func func = _ => + { + delegateInvocations++; + return valueToReturn; + }; - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - [Fact] - public void Should_allow_custom_FuncCacheKeyStrategy() - { - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + [Fact] + public void Should_allow_custom_FuncCacheKeyStrategy() + { + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - var person1 = new object(); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - var person2 = new object(); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + var person1 = new object(); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + var person2 = new object(); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; - Func func = _ => { funcExecuted = true; return new object(); }; + var funcExecuted = false; + Func func = _ => { funcExecuted = true; return new object(); }; - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - [Fact] - public void Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + [Fact] + public void Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - var person1 = new object(); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - var person2 = new object(); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + var person1 = new object(); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + var person2 = new object(); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; - Func func = _ => { funcExecuted = true; return new object(); }; + var funcExecuted = false; + Func func = _ => { funcExecuted = true; return new object(); }; - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Caching behaviours, default(TResult) + #region Caching behaviours, default(TResult) - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() - { - ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + var valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Non-generic CachePolicy in non-generic PolicyWrap + #region Non-generic CachePolicy in non-generic PolicyWrap - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(cache, noop); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + var wrap = Policy.Wrap(cache, noop); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - wrap.Execute(_ => + wrap.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(noop, cache); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + var wrap = Policy.Wrap(noop, cache); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - wrap.Execute(_ => + wrap.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(noop, cache, noop); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + var wrap = Policy.Wrap(noop, cache, noop); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - wrap.Execute(_ => + wrap.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); - - delegateExecuted.Should().BeFalse(); - } + .Should().Be(valueToReturnFromCache); - #endregion + delegateExecuted.Should().BeFalse(); + } - #region No-op pass-through behaviour + #endregion - [Fact] - public void Should_always_execute_delegate_if_execution_key_not_set() - { - var valueToReturn = Guid.NewGuid().ToString(); + #region No-op pass-through behaviour - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - - var delegateInvocations = 0; - var func = () => + [Fact] + public void Should_always_execute_delegate_if_execution_key_not_set() { - delegateInvocations++; - return valueToReturn; - }; - - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + var valueToReturn = Guid.NewGuid().ToString(); - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - [Fact] - public void Should_always_execute_delegate_if_execution_is_void_returning() - { - var operationKey = "SomeKey"; + var delegateInvocations = 0; + var func = () => + { + delegateInvocations++; + return valueToReturn; + }; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - var delegateInvocations = 0; - Action action = _ => { delegateInvocations++; }; + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - cache.Execute(action, new Context(operationKey)); - delegateInvocations.Should().Be(1); + [Fact] + public void Should_always_execute_delegate_if_execution_is_void_returning() + { + var operationKey = "SomeKey"; - cache.Execute(action, new Context(operationKey)); - delegateInvocations.Should().Be(2); - } + var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - #endregion + var delegateInvocations = 0; + Action action = _ => { delegateInvocations++; }; - #region Cancellation + cache.Execute(action, new Context(operationKey)); + delegateInvocations.Should().Be(1); - [Fact] - public void Should_honour_cancellation_even_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + cache.Execute(action, new Context(operationKey)); + delegateInvocations.Should().Be(2); + } - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + #endregion - var tokenSource = new CancellationTokenSource(); + #region Cancellation - var delegateInvocations = 0; - Func func = (_, _) => + [Fact] + public void Should_honour_cancellation_even_if_prior_execution_has_cached() { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - return valueToReturn; - }; + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - tokenSource.Cancel(); + var tokenSource = new CancellationTokenSource(); - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - delegateInvocations.Should().Be(1); - } + var delegateInvocations = 0; + Func func = (_, _) => + { + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + return valueToReturn; + }; - [Fact] - public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + tokenSource.Cancel(); - var tokenSource = new CancellationTokenSource(); + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + delegateInvocations.Should().Be(1); + } - Func func = (_, ct) => + [Fact] + public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - return valueToReturn; - }; - - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - #endregion - - #region Policy hooks - - [Fact] - public void Should_call_onError_delegate_if_cache_get_errors() - { - var ex = new Exception(); - ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Exception exceptionFromCacheProvider = null; + var tokenSource = new CancellationTokenSource(); - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + Func func = (_, ct) => + { + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + return valueToReturn; + }; - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + #endregion - var delegateExecuted = false; + #region Policy hooks + [Fact] + public void Should_call_onError_delegate_if_cache_get_errors() + { + var ex = new Exception(); + ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); - // Even though value is in cache, get will error; so value is returned from execution. - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, new Context(operationKey)) - .Should().Be(valueToReturnFromExecution); - delegateExecuted.Should().BeTrue(); + Exception exceptionFromCacheProvider = null; - // And error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); - } + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - [Fact] - public void Should_call_onError_delegate_if_cache_put_errors() - { - var ex = new Exception(); - ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - Exception exceptionFromCacheProvider = null; + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; + var delegateExecuted = false; - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + // Even though value is in cache, get will error; so value is returned from execution. + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, new Context(operationKey)) + .Should().Be(valueToReturnFromExecution); + delegateExecuted.Should().BeTrue(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + // And error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); + } - // error should be captured by onError delegate. - exceptionFromCacheProvider.Should().Be(ex); + [Fact] + public void Should_call_onError_delegate_if_cache_put_errors() + { + var ex = new Exception(); + ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); - // failed to put it in the cache - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); + Exception exceptionFromCacheProvider = null; - } + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - [Fact] - public void Should_execute_oncacheget_after_got_from_cache() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; + Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - const string operationKey = "SomeOperationKey"; - string keyPassedToDelegate = null; + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - var contextToExecute = new Context(operationKey); - Context contextPassedToDelegate = null; + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + // error should be captured by onError delegate. + exceptionFromCacheProvider.Should().Be(ex); - var delegateExecuted = false; - cache.Execute(_ => - { - delegateExecuted = true; - return valueToReturnFromExecution; - }, contextToExecute) - .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); + // failed to put it in the cache + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); - contextPassedToDelegate.Should().BeSameAs(contextToExecute); - keyPassedToDelegate.Should().Be(operationKey); - } + } - [Fact] - public void Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() - { - const string valueToReturn = "valueToReturn"; + [Fact] + public void Should_execute_oncacheget_after_got_from_cache() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + + const string operationKey = "SomeOperationKey"; + string keyPassedToDelegate = null; + + var contextToExecute = new Context(operationKey); + Context contextPassedToDelegate = null; + + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; + + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + + var delegateExecuted = false; + cache.Execute(_ => + { + delegateExecuted = true; + return valueToReturnFromExecution; + }, contextToExecute) + .Should().Be(valueToReturnFromCache); + delegateExecuted.Should().BeFalse(); + + contextPassedToDelegate.Should().BeSameAs(contextToExecute); + keyPassedToDelegate.Should().Be(operationKey); + } + + [Fact] + public void Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_value_and_put() + { + const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + var contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); - keyPassedToOnCacheMiss.Should().Be(operationKey); + contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); + keyPassedToOnCacheMiss.Should().Be(operationKey); - contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); - keyPassedToOnCachePut.Should().Be(operationKey); - } + contextPassedToOnCachePut.Should().BeSameAs(contextToExecute); + keyPassedToOnCachePut.Should().Be(operationKey); + } - [Fact] - public void Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; + [Fact] + public void Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold_value_and_returned_value_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; - string keyPassedToOnCacheMiss = null; - string keyPassedToOnCachePut = null; + const string operationKey = "SomeOperationKey"; + string keyPassedToOnCacheMiss = null; + string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); - Context contextPassedToOnCacheMiss = null; - Context contextPassedToOnCachePut = null; + var contextToExecute = new Context(operationKey); + Context contextPassedToOnCacheMiss = null; + Context contextPassedToOnCachePut = null; - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; - Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; - Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; + Action onCacheMiss = (ctx, key) => { contextPassedToOnCacheMiss = ctx; keyPassedToOnCacheMiss = key; }; + Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); - contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); - keyPassedToOnCacheMiss.Should().Be(operationKey); + contextPassedToOnCacheMiss.Should().BeSameAs(contextToExecute); + keyPassedToOnCacheMiss.Should().Be(operationKey); - contextPassedToOnCachePut.Should().BeNull(); - keyPassedToOnCachePut.Should().BeNull(); - } + contextPassedToOnCachePut.Should().BeNull(); + keyPassedToOnCachePut.Should().BeNull(); + } - [Fact] - public void Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() - { - var valueToReturn = Guid.NewGuid().ToString(); + [Fact] + public void Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() + { + var valueToReturn = Guid.NewGuid().ToString(); - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - var onCacheMissExecuted = false; - Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; + var onCacheMissExecuted = false; + Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; - var cache = Policy.Cache(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); + var cache = Policy.Cache(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); - cache.Execute(() => valueToReturn /*, no operation key */).Should().Be(valueToReturn); + cache.Execute(() => valueToReturn /*, no operation key */).Should().Be(valueToReturn); - onCacheMissExecuted.Should().BeFalse(); - } + onCacheMissExecuted.Should().BeFalse(); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs index d29202cc882..705ff4414a4 100644 --- a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs @@ -8,493 +8,494 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CacheTResultAsyncSpecs : IDisposable +namespace Polly.Specs.Caching { - #region Configuration - - [Fact] - public void Should_throw_when_cache_provider_is_null() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CacheTResultAsyncSpecs : IDisposable { - IAsyncCacheProvider cacheProvider = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } + #region Configuration - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } + [Fact] + public void Should_throw_when_cache_provider_is_null() + { + IAsyncCacheProvider cacheProvider = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } - #endregion + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } + + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } + #endregion - #region Caching behaviours + #region Caching behaviours - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await cache.ExecuteAsync(async _ => + (await cache.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); - var cache = Policy.CacheAsync(stubCacheProvider, ttl); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var ttl = TimeSpan.FromMinutes(30); + var cache = Policy.CacheAsync(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - var delegateInvocations = 0; - Func> func = async _ => - { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; - - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - - // First execution should execute delegate and put result in the cache. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + var delegateInvocations = 0; + Func> func = async _ => + { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - [Fact] - public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); + // First execution should execute delegate and put result in the cache. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } + + [Fact] + public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); + + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - var delegateInvocations = 0; - Func> func = async _ => + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } + + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; + + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + var delegateInvocations = 0; + Func> func = async _ => + { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - [Fact] - public async Task Should_allow_custom_FuncCacheKeyStrategy() - { - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + [Fact] + public async Task Should_allow_custom_FuncCacheKeyStrategy() + { + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - var funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + var funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - [Fact] - public async Task Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - //var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - var cache = Policy.CacheAsync(stubCacheProvider.AsyncFor(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + [Fact] + public async Task Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + //var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + var cache = Policy.CacheAsync(stubCacheProvider.AsyncFor(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - var funcExecuted = false; - Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + var funcExecuted = false; + Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - #endregion + (await cache.ExecuteAsync(func, new Context("person", new { id = "2" }.AsDictionary()))).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - #region Caching behaviours, default(TResult) + #endregion - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + #region Caching behaviours, default(TResult) - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await cache.ExecuteAsync(async _ => + (await cache.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() - { - ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + var valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await cache.ExecuteAsync(async _ => + (await cache.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Generic CachePolicy in PolicyWrap + #region Generic CachePolicy in PolicyWrap - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = cache.WrapAsync(noop); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = cache.WrapAsync(noop); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await wrap.ExecuteAsync(async _ => + (await wrap.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = noop.WrapAsync(cache); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = noop.WrapAsync(cache); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await wrap.ExecuteAsync(async _ => + (await wrap.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var noop = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(noop, cache, noop); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + var noop = Policy.NoOpAsync(); + var wrap = Policy.WrapAsync(noop, cache, noop); - await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); + await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + var delegateExecuted = false; - (await wrap.ExecuteAsync(async _ => + (await wrap.ExecuteAsync(async _ => { delegateExecuted = true; await TaskHelper.EmptyTask; return valueToReturnFromExecution; }, new Context(operationKey))) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region No-op pass-through behaviour + #region No-op pass-through behaviour - [Fact] - public async Task Should_always_execute_delegate_if_execution_key_not_set() - { - var valueToReturn = Guid.NewGuid().ToString(); + [Fact] + public async Task Should_always_execute_delegate_if_execution_key_not_set() + { + var valueToReturn = Guid.NewGuid().ToString(); - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - var func = async () => { - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + var delegateInvocations = 0; + var func = async () => { + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + (await cache.ExecuteAsync(func /*, no operation key */)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - #endregion + #endregion - #region Cancellation + #region Cancellation - [Fact] - public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_honour_cancellation_even_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + var tokenSource = new CancellationTokenSource(); - var delegateInvocations = 0; - Func> func = async (_, _) => - { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - await TaskHelper.EmptyTask; - return valueToReturn; - }; + var delegateInvocations = 0; + Func> func = async (_, _) => + { + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + await TaskHelper.EmptyTask; + return valueToReturn; + }; - (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + (await cache.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - tokenSource.Cancel(); + tokenSource.Cancel(); - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - delegateInvocations.Should().Be(1); - } + cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + delegateInvocations.Should().Be(1); + } - [Fact] - public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + var tokenSource = new CancellationTokenSource(); - Func> func = async (_, ct) => - { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - await TaskHelper.EmptyTask; - return valueToReturn; - }; - - cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + Func> func = async (_, ct) => + { + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + await TaskHelper.EmptyTask; + return valueToReturn; + }; - #endregion + cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); - public void Dispose() - { - SystemClock.Reset(); + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } + + #endregion + + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Caching/CacheTResultSpecs.cs b/src/Polly.Specs/Caching/CacheTResultSpecs.cs index 504e1f1127e..34076be486f 100644 --- a/src/Polly.Specs/Caching/CacheTResultSpecs.cs +++ b/src/Polly.Specs/Caching/CacheTResultSpecs.cs @@ -8,483 +8,484 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Caching; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CacheTResultSpecs : IDisposable +namespace Polly.Specs.Caching { - #region Configuration - - [Fact] - public void Should_throw_when_cache_provider_is_null() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CacheTResultSpecs : IDisposable { - ISyncCacheProvider cacheProvider = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); - } + #region Configuration - [Fact] - public void Should_throw_when_ttl_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - ITtlStrategy ttlStrategy = null; - Action action = () => Policy.Cache(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); - } - [Fact] - public void Should_throw_when_cache_key_strategy_is_null() - { - ISyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null; - Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); - } + [Fact] + public void Should_throw_when_cache_provider_is_null() + { + ISyncCacheProvider cacheProvider = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue); + action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + } - #endregion + [Fact] + public void Should_throw_when_ttl_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + ITtlStrategy ttlStrategy = null; + Action action = () => Policy.Cache(cacheProvider, ttlStrategy); + action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + } + [Fact] + public void Should_throw_when_cache_key_strategy_is_null() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + Func cacheKeyStrategy = null; + Action action = () => Policy.Cache(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); + action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + } - #region Caching behaviours + #endregion - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + #region Caching behaviours + + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - cache.Execute(_ => + cache.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_execute_delegate_again() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); - var cache = Policy.Cache(stubCacheProvider, ttl); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var ttl = TimeSpan.FromMinutes(30); + var cache = Policy.Cache(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - var delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; + var delegateInvocations = 0; + Func func = _ => + { + delegateInvocations++; + return valueToReturn; + }; - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime; + var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime; - // First execution should execute delegate and put result in the cache. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + // First execution should execute delegate and put result in the cache. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); - // Second execution (before cache expires) should get it from the cache - no further delegate execution. - // (Manipulate time so just prior cache expiry). - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + // Second execution (before cache expires) should get it from the cache - no further delegate execution. + // (Manipulate time so just prior cache expiry). + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(-1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - // Manipulate time to force cache expiry. - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); + // Manipulate time to force cache expiry. + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(ttl).AddSeconds(1); - // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } + // Third execution (cache expired) should not get it from the cache - should cause further delegate execution. + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - [Fact] - public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not_hold_value_but_ttl_indicates_not_worth_caching() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeFalse(); - fromCache2.Should().BeNull(); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeFalse(); + fromCache2.Should().BeNull(); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_execution_has_cached() + { + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - Func func = _ => - { - delegateInvocations++; - return valueToReturn; - }; + var delegateInvocations = 0; + Func func = _ => + { + delegateInvocations++; + return valueToReturn; + }; - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); - } + cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); + } - [Fact] - public void Should_allow_custom_FuncICacheKeyStrategy() - { + [Fact] + public void Should_allow_custom_FuncICacheKeyStrategy() + { - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; - Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + var funcExecuted = false; + Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - [Fact] - public void Should_allow_custom_ICacheKeyStrategy() - { - Action noErrorHandling = (_, _, _) => { }; - Action emptyDelegate = (_, _) => { }; + [Fact] + public void Should_allow_custom_ICacheKeyStrategy() + { + Action noErrorHandling = (_, _, _) => { }; + Action emptyDelegate = (_, _) => { }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - var cache = Policy.Cache(stubCacheProvider.For(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + var cache = Policy.Cache(stubCacheProvider.For(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - object person1 = new ResultClass(ResultPrimitive.Good, "person1"); - stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - object person2 = new ResultClass(ResultPrimitive.Good, "person2"); - stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); + object person1 = new ResultClass(ResultPrimitive.Good, "person1"); + stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); + object person2 = new ResultClass(ResultPrimitive.Good, "person2"); + stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; - Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; + var funcExecuted = false; + Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; - cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); - funcExecuted.Should().BeFalse(); + cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); + funcExecuted.Should().BeFalse(); - cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); - funcExecuted.Should().BeFalse(); - } + cache.Execute(func, new Context("person", new { id = "2" }.AsDictionary())).Should().BeSameAs(person2); + funcExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Caching behaviours, default(TResult) + #region Caching behaviours, default(TResult) - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() - { - ResultClass valueToReturn = default; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_reference_type() + { + ResultClass valueToReturn = default; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() - { - ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() + { + ResultClass valueToReturnFromCache = default; + var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - cache.Execute(_ => + cache.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() - { - ResultPrimitive valueToReturn = default; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hold_value__default_for_value_type() + { + ResultPrimitive valueToReturn = default; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() - { - ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; - valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() + { + ResultPrimitive valueToReturnFromCache = default; + var valueToReturnFromExecution = ResultPrimitive.Good; + valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - cache.Execute(_ => + cache.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Generic CachePolicy in PolicyWrap + #region Generic CachePolicy in PolicyWrap - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_outermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - var wrap = cache.Wrap(noop); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + var wrap = cache.Wrap(noop); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - wrap.Execute(_ => + wrap.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_innermost_in_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - var wrap = noop.Wrap(cache); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + var wrap = noop.Wrap(cache); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - wrap.Execute(_ => + wrap.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } + delegateExecuted.Should().BeFalse(); + } - [Fact] - public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() - { - const string valueToReturnFromCache = "valueToReturnFromCache"; - const string valueToReturnFromExecution = "valueToReturnFromExecution"; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value_when_mid_policywrap() + { + const string valueToReturnFromCache = "valueToReturnFromCache"; + const string valueToReturnFromExecution = "valueToReturnFromExecution"; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(noop, cache, noop); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + Policy noop = Policy.NoOp(); + var wrap = Policy.Wrap(noop, cache, noop); - stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); + stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + var delegateExecuted = false; - wrap.Execute(_ => + wrap.Execute(_ => { delegateExecuted = true; return valueToReturnFromExecution; }, new Context(operationKey)) - .Should().Be(valueToReturnFromCache); + .Should().Be(valueToReturnFromCache); - delegateExecuted.Should().BeFalse(); - } - - #endregion + delegateExecuted.Should().BeFalse(); + } - #region No-op pass-through behaviour + #endregion - [Fact] - public void Should_always_execute_delegate_if_execution_key_not_set() - { - var valueToReturn = Guid.NewGuid().ToString(); + #region No-op pass-through behaviour - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - - var delegateInvocations = 0; - var func = () => + [Fact] + public void Should_always_execute_delegate_if_execution_key_not_set() { - delegateInvocations++; - return valueToReturn; - }; + var valueToReturn = Guid.NewGuid().ToString(); - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); - delegateInvocations.Should().Be(2); - } - - #endregion + var delegateInvocations = 0; + var func = () => + { + delegateInvocations++; + return valueToReturn; + }; - #region Cancellation + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - [Fact] - public void Should_honour_cancellation_even_if_prior_execution_has_cached() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + cache.Execute(func /*, no operation key */).Should().Be(valueToReturn); + delegateInvocations.Should().Be(2); + } - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + #endregion - var tokenSource = new CancellationTokenSource(); + #region Cancellation - var delegateInvocations = 0; - Func func = (_, _) => + [Fact] + public void Should_honour_cancellation_even_if_prior_execution_has_cached() { - // delegate does not observe cancellation token; test is whether CacheEngine does. - delegateInvocations++; - return valueToReturn; - }; + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); - delegateInvocations.Should().Be(1); + var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - tokenSource.Cancel(); + var tokenSource = new CancellationTokenSource(); - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); - delegateInvocations.Should().Be(1); - } + var delegateInvocations = 0; + Func func = (_, _) => + { + // delegate does not observe cancellation token; test is whether CacheEngine does. + delegateInvocations++; + return valueToReturn; + }; - [Fact] - public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() - { - const string valueToReturn = "valueToReturn"; - const string operationKey = "SomeOperationKey"; + cache.Execute(func, new Context(operationKey), tokenSource.Token).Should().Be(valueToReturn); + delegateInvocations.Should().Be(1); - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + tokenSource.Cancel(); - var tokenSource = new CancellationTokenSource(); + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + delegateInvocations.Should().Be(1); + } - Func func = (_, ct) => + [Fact] + public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_cache() { - tokenSource.Cancel(); // simulate cancellation raised during delegate execution - ct.ThrowIfCancellationRequested(); - return valueToReturn; - }; + const string valueToReturn = "valueToReturn"; + const string operationKey = "SomeOperationKey"; - cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) - .Should().Throw(); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); - } + var tokenSource = new CancellationTokenSource(); - #endregion + Func func = (_, ct) => + { + tokenSource.Cancel(); // simulate cancellation raised during delegate execution + ct.ThrowIfCancellationRequested(); + return valueToReturn; + }; - public void Dispose() - { - SystemClock.Reset(); + cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) + .Should().Throw(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); + } + + #endregion + + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/ContextualTtlSpecs.cs b/src/Polly.Specs/Caching/ContextualTtlSpecs.cs index b9ea3511bb4..8eceec5808c 100644 --- a/src/Polly.Specs/Caching/ContextualTtlSpecs.cs +++ b/src/Polly.Specs/Caching/ContextualTtlSpecs.cs @@ -4,49 +4,50 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching; - -public class ContextualTtlSpecs +namespace Polly.Specs.Caching { - [Fact] - public void Should_return_zero_if_no_value_set_on_context() - { - new ContextualTtl().GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); - } - - [Fact] - public void Should_return_zero_if_invalid_value_set_on_context() + public class ContextualTtlSpecs { - var contextData = new Dictionary(); - contextData[ContextualTtl.TimeSpanKey] = new object(); - - var context = new Context(String.Empty, contextData); - new ContextualTtl().GetTtl(context, null).Timespan.Should().Be(TimeSpan.Zero); - } - - [Fact] - public void Should_return_value_set_on_context() - { - var ttl = TimeSpan.FromSeconds(30); - var contextData = new Dictionary(); - contextData[ContextualTtl.TimeSpanKey] = ttl; - - var context = new Context(String.Empty, contextData); - var gotTtl = new ContextualTtl().GetTtl(context, null); - gotTtl.Timespan.Should().Be(ttl); - gotTtl.SlidingExpiration.Should().BeFalse(); - } - - [Fact] - public void Should_return_negative_value_set_on_context() - { - var ttl = TimeSpan.FromTicks(-1); - var contextData = new Dictionary(); - contextData[ContextualTtl.TimeSpanKey] = ttl; - - var context = new Context(String.Empty, contextData); - var gotTtl = new ContextualTtl().GetTtl(context, null); - gotTtl.Timespan.Should().Be(ttl); - gotTtl.SlidingExpiration.Should().BeFalse(); + [Fact] + public void Should_return_zero_if_no_value_set_on_context() + { + new ContextualTtl().GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); + } + + [Fact] + public void Should_return_zero_if_invalid_value_set_on_context() + { + var contextData = new Dictionary(); + contextData[ContextualTtl.TimeSpanKey] = new object(); + + var context = new Context(String.Empty, contextData); + new ContextualTtl().GetTtl(context, null).Timespan.Should().Be(TimeSpan.Zero); + } + + [Fact] + public void Should_return_value_set_on_context() + { + var ttl = TimeSpan.FromSeconds(30); + var contextData = new Dictionary(); + contextData[ContextualTtl.TimeSpanKey] = ttl; + + var context = new Context(String.Empty, contextData); + var gotTtl = new ContextualTtl().GetTtl(context, null); + gotTtl.Timespan.Should().Be(ttl); + gotTtl.SlidingExpiration.Should().BeFalse(); + } + + [Fact] + public void Should_return_negative_value_set_on_context() + { + var ttl = TimeSpan.FromTicks(-1); + var contextData = new Dictionary(); + contextData[ContextualTtl.TimeSpanKey] = ttl; + + var context = new Context(String.Empty, contextData); + var gotTtl = new ContextualTtl().GetTtl(context, null); + gotTtl.Timespan.Should().Be(ttl); + gotTtl.SlidingExpiration.Should().BeFalse(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs b/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs index 49badefd935..40857daf8c1 100644 --- a/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs +++ b/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs @@ -2,18 +2,19 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching; - -public class DefaultCacheKeyStrategySpecs +namespace Polly.Specs.Caching { - [Fact] - public void Should_return_Context_OperationKey_as_cache_key() + public class DefaultCacheKeyStrategySpecs { - var operationKey = "SomeKey"; + [Fact] + public void Should_return_Context_OperationKey_as_cache_key() + { + var operationKey = "SomeKey"; - var context = new Context(operationKey); + var context = new Context(operationKey); - DefaultCacheKeyStrategy.Instance.GetCacheKey(context) - .Should().Be(operationKey); + DefaultCacheKeyStrategy.Instance.GetCacheKey(context) + .Should().Be(operationKey); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs b/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs index 657bfa159c2..d3182905b5c 100644 --- a/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs @@ -8,61 +8,62 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class GenericCacheProviderAsyncSpecs : IDisposable +namespace Polly.Specs.Caching { - [Fact] - public async Task Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() + [Collection(Constants.SystemClockDependentTestCollection)] + public class GenericCacheProviderAsyncSpecs : IDisposable { - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() + { + const string operationKey = "SomeOperationKey"; - var onErrorCalled = false; - Action onError = (_, _, _) => { onErrorCalled = true; }; + var onErrorCalled = false; + Action onError = (_, _, _) => { onErrorCalled = true; }; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - var result = await cache.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - return ResultPrimitive.Substitute; - }, new Context(operationKey)); + var result = await cache.ExecuteAsync(async _ => + { + await TaskHelper.EmptyTask; + return ResultPrimitive.Substitute; + }, new Context(operationKey)); - onErrorCalled.Should().BeFalse(); - } + onErrorCalled.Should().BeFalse(); + } - [Fact] - public async Task Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() - { - const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; - const string operationKey = "SomeOperationKey"; + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() + { + const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; + const string operationKey = "SomeOperationKey"; - IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - (await cache.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - return ResultPrimitive.Substitute; - }, new Context(operationKey))).Should().Be(valueToReturn); + (await cache.ExecuteAsync(async _ => + { + await TaskHelper.EmptyTask; + return ResultPrimitive.Substitute; + }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs b/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs index 2715a9483b9..8b6b457b6da 100644 --- a/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs +++ b/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs @@ -6,55 +6,56 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class GenericCacheProviderSpecs : IDisposable +namespace Polly.Specs.Caching { - [Fact] - public void Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() + [Collection(Constants.SystemClockDependentTestCollection)] + public class GenericCacheProviderSpecs : IDisposable { - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() + { + const string operationKey = "SomeOperationKey"; - var onErrorCalled = false; - Action onError = (_, _, _) => { onErrorCalled = true; }; + var onErrorCalled = false; + Action onError = (_, _, _) => { onErrorCalled = true; }; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); - cacheHit.Should().BeFalse(); - fromCache.Should().BeNull(); + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + cacheHit.Should().BeFalse(); + fromCache.Should().BeNull(); - var result = cache.Execute(_ => ResultPrimitive.Substitute, new Context(operationKey)); + var result = cache.Execute(_ => ResultPrimitive.Substitute, new Context(operationKey)); - onErrorCalled.Should().BeFalse(); - } + onErrorCalled.Should().BeFalse(); + } - [Fact] - public void Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() - { - const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; - const string operationKey = "SomeOperationKey"; + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() + { + const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; + const string operationKey = "SomeOperationKey"; - ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); - cacheHit1.Should().BeFalse(); - fromCache1.Should().BeNull(); + cacheHit1.Should().BeFalse(); + fromCache1.Should().BeNull(); - cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); + cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); - cacheHit2.Should().BeTrue(); - fromCache2.Should().Be(valueToReturn); - } + cacheHit2.Should().BeTrue(); + fromCache2.Should().Be(valueToReturn); + } - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs index d00443be42f..d1aa5aeb025 100644 --- a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs +++ b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs @@ -4,59 +4,60 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Caching; - -public class RelativeTtlSpecs +namespace Polly.Specs.Caching { - [Fact] - public void Should_throw_when_timespan_is_less_than_zero() + public class RelativeTtlSpecs { - Action configure = () => new RelativeTtl(TimeSpan.FromMilliseconds(-1)); + [Fact] + public void Should_throw_when_timespan_is_less_than_zero() + { + Action configure = () => new RelativeTtl(TimeSpan.FromMilliseconds(-1)); - configure.Should().Throw().And.ParamName.Should().Be("ttl"); - } + configure.Should().Throw().And.ParamName.Should().Be("ttl"); + } - [Fact] - public void Should_not_throw_when_timespan_is_zero() - { - Action configure = () => new RelativeTtl(TimeSpan.Zero); + [Fact] + public void Should_not_throw_when_timespan_is_zero() + { + Action configure = () => new RelativeTtl(TimeSpan.Zero); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_allow_timespan_max_value() - { - Action configure = () => new RelativeTtl(TimeSpan.MaxValue); + [Fact] + public void Should_allow_timespan_max_value() + { + Action configure = () => new RelativeTtl(TimeSpan.MaxValue); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_return_configured_timespan() - { - var ttl = TimeSpan.FromSeconds(30); + [Fact] + public void Should_return_configured_timespan() + { + var ttl = TimeSpan.FromSeconds(30); - var ttlStrategy = new RelativeTtl(ttl); + var ttlStrategy = new RelativeTtl(ttl); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().BeCloseTo(ttl); - retrieved.SlidingExpiration.Should().BeFalse(); - } + var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + retrieved.Timespan.Should().BeCloseTo(ttl); + retrieved.SlidingExpiration.Should().BeFalse(); + } - [Fact] - public void Should_return_configured_timespan_from_time_requested() - { - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); - var ttl = TimeSpan.FromSeconds(30); - var delay = TimeSpan.FromSeconds(5); + [Fact] + public void Should_return_configured_timespan_from_time_requested() + { + var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + var ttl = TimeSpan.FromSeconds(30); + var delay = TimeSpan.FromSeconds(5); - var ttlStrategy = new RelativeTtl(ttl); + var ttlStrategy = new RelativeTtl(ttl); - SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(delay); + SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(delay); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().BeCloseTo(ttl); - retrieved.SlidingExpiration.Should().BeFalse(); + var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + retrieved.Timespan.Should().BeCloseTo(ttl); + retrieved.SlidingExpiration.Should().BeFalse(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/ResultTtlSpecs.cs b/src/Polly.Specs/Caching/ResultTtlSpecs.cs index aca14c5acd6..4dff878c7c5 100644 --- a/src/Polly.Specs/Caching/ResultTtlSpecs.cs +++ b/src/Polly.Specs/Caching/ResultTtlSpecs.cs @@ -3,66 +3,67 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching; - -public class ResultTtlSpecs +namespace Polly.Specs.Caching { - [Fact] - public void Should_throw_when_func_is_null() + public class ResultTtlSpecs { - Action configure = () => new ResultTtl((Func)null); + [Fact] + public void Should_throw_when_func_is_null() + { + Action configure = () => new ResultTtl((Func)null); - configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); - } + configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); + } - [Fact] - public void Should_throw_when_func_is_null_using_context() - { - Action configure = () => new ResultTtl((Func)null); + [Fact] + public void Should_throw_when_func_is_null_using_context() + { + Action configure = () => new ResultTtl((Func)null); - configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); - } + configure.Should().Throw().And.ParamName.Should().Be("ttlFunc"); + } - [Fact] - public void Should_not_throw_when_func_is_set() - { - Action configure = () => new ResultTtl(_ => new Ttl()); + [Fact] + public void Should_not_throw_when_func_is_set() + { + Action configure = () => new ResultTtl(_ => new Ttl()); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_func_is_set_using_context() - { - Action configure = () => new ResultTtl((_, _) => new Ttl()); + [Fact] + public void Should_not_throw_when_func_is_set_using_context() + { + Action configure = () => new ResultTtl((_, _) => new Ttl()); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_return_func_result() - { - var ttl = TimeSpan.FromMinutes(1); - Func func = result => new Ttl(result.Ttl); + [Fact] + public void Should_return_func_result() + { + var ttl = TimeSpan.FromMinutes(1); + Func func = result => new Ttl(result.Ttl); - var ttlStrategy = new ResultTtl(func); + var ttlStrategy = new ResultTtl(func); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }); - retrieved.Timespan.Should().Be(ttl); - retrieved.SlidingExpiration.Should().BeFalse(); - } + var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }); + retrieved.Timespan.Should().Be(ttl); + retrieved.SlidingExpiration.Should().BeFalse(); + } - [Fact] - public void Should_return_func_result_using_context() - { - const string specialKey = "specialKey"; + [Fact] + public void Should_return_func_result_using_context() + { + const string specialKey = "specialKey"; - var ttl = TimeSpan.FromMinutes(1); - Func func = (context, result) => context.OperationKey == specialKey ? new Ttl(TimeSpan.Zero) : new Ttl(result.Ttl); + var ttl = TimeSpan.FromMinutes(1); + Func func = (context, result) => context.OperationKey == specialKey ? new Ttl(TimeSpan.Zero) : new Ttl(result.Ttl); - var ttlStrategy = new ResultTtl(func); + var ttlStrategy = new ResultTtl(func); - ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }).Timespan.Should().Be(ttl); - ttlStrategy.GetTtl(new Context(specialKey), new { Ttl = ttl }).Timespan.Should().Be(TimeSpan.Zero); + ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }).Timespan.Should().Be(ttl); + ttlStrategy.GetTtl(new Context(specialKey), new { Ttl = ttl }).Timespan.Should().Be(TimeSpan.Zero); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs b/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs index 7e2d3f2e75d..2501ee74821 100644 --- a/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs @@ -7,447 +7,448 @@ using Polly.Specs.Helpers.Caching; using Xunit; -namespace Polly.Specs.Caching; - -public class AsyncSerializingCacheProviderSpecs +namespace Polly.Specs.Caching { - #region Object-to-TSerialized serializer - - [Fact] - public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - var stubObjectSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => s.Original - ); - - Action configure = () => new AsyncSerializingCacheProvider(null, stubObjectSerializer); - - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); - } - - [Fact] - public void Single_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Single_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put() - { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() - { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - #endregion - - #region TResult-to-TSerialized serializer - - [Fact] - public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => s.Original - ); - - Action configure = () => new AsyncSerializingCacheProvider>(null, stubTResultSerializer); - - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); - } - - [Fact] - public void Double_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Double_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - (var cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + public class AsyncSerializingCacheProviderSpecs { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + #region Object-to-TSerialized serializer + + [Fact] + public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + var stubObjectSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => s.Original + ); + + Action configure = () => new AsyncSerializingCacheProvider(null, stubObjectSerializer); + + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Single_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Single_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + #endregion + + #region TResult-to-TSerialized serializer + + [Fact] + public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => s.Original + ); + + Action configure = () => new AsyncSerializingCacheProvider>(null, stubTResultSerializer); + + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Double_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new AsyncSerializingCacheProvider(new StubCacheProvider().AsyncFor(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Double_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().AsyncFor().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + (var cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + + await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + var serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); + } + + #endregion } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - - await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - var serializingCacheProvider = - stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - #endregion } \ No newline at end of file diff --git a/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs b/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs index 4b2c6c10cb6..d5cfc753ed9 100644 --- a/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs +++ b/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs @@ -5,449 +5,450 @@ using Polly.Specs.Helpers.Caching; using Xunit; -namespace Polly.Specs.Caching; - -public class SerializingCacheProviderSpecs +namespace Polly.Specs.Caching { - #region Object-to-TSerialized serializer - - [Fact] - public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - var stubObjectSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => s.Original - ); - - Action configure = () => new SerializingCacheProvider(null, stubObjectSerializer); - - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); - } - - [Fact] - public void Single_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Single_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().For().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_serialize_on_put() - { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o);}, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + public class SerializingCacheProviderSpecs { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - object objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); + #region Object-to-TSerialized serializer - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType() - .Which.Original.Should().Be(objectToCache); - } + [Fact] + public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + var stubObjectSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => s.Original + ); - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default); - } - - #endregion - - #region TResult-to-TSerialized serializer + Action configure = () => new SerializingCacheProvider(null, stubObjectSerializer); - [Fact] - public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() - { - var stubTResultSerializer = new StubSerializer>( + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Single_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Single_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().For().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_serialize_on_put() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o);}, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + + var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + object objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = new object(); + var key = "some key"; + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + + var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default); + } + + #endregion + + #region TResult-to-TSerialized serializer + + [Fact] + public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() + { + var stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => s.Original - ); + ); Action configure = () => new SerializingCacheProvider>(null, stubTResultSerializer); - configure.Should().Throw() - .And.ParamName.Should().Be("wrappedCacheProvider"); + configure.Should().Throw() + .And.ParamName.Should().Be("wrappedCacheProvider"); + } + + [Fact] + public void Double_generic_constructor_should_throw_on_no_serializer() + { + Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Double_generic_extension_syntax_should_throw_on_no_serializer() + { + Action configure = () => new StubCacheProvider().For().WithSerializer(null); + + configure.Should().Throw() + .And.ParamName.Should().Be("serializer"); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_serialize_on_put() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + (var cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); + + var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() + { + var serializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + var stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default; + var key = "some key"; + + var serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().BeTrue(); + + (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + fromCache.Should().BeOfType>() + .Which.Original.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var objectToCache = ResultPrimitive.Good; + var key = "some key"; + + var serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + + stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); + (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeTrue(); + deserializeInvoked.Should().BeTrue(); + fromCache.Should().Be(objectToCache); + } + + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + var deserializeInvoked = false; + var stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + var key = "some key"; + + stubCacheProvider.TryGet(key).Item2.Should().BeNull(); + + var serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + + cacheHit.Should().BeFalse(); + deserializeInvoked.Should().BeFalse(); + fromCache.Should().Be(default(ResultPrimitive)); + } + + #endregion } - - [Fact] - public void Double_generic_constructor_should_throw_on_no_serializer() - { - Action configure = () => new SerializingCacheProvider(new StubCacheProvider().For(), null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Double_generic_extension_syntax_should_throw_on_no_serializer() - { - Action configure = () => new StubCacheProvider().For().WithSerializer(null); - - configure.Should().Throw() - .And.ParamName.Should().Be("serializer"); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_serialize_on_put() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - (var cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() - { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() - { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, - deserialize: s => s.Original - ); - var stubCacheProvider = new StubCacheProvider(); - ResultPrimitive objectToCache = default; - var key = "some key"; - - var serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - - serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); - - serializeInvoked.Should().BeTrue(); - - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - fromCache.Should().BeOfType>() - .Which.Original.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() - { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; - - var serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - - stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeTrue(); - deserializeInvoked.Should().BeTrue(); - fromCache.Should().Be(objectToCache); - } - - [Fact] - public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() - { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( - serialize: o => new StubSerialized(o), - deserialize: s => { deserializeInvoked = true; return s.Original; } - ); - var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; - - stubCacheProvider.TryGet(key).Item2.Should().BeNull(); - - var serializingCacheProvider = - stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); - - cacheHit.Should().BeFalse(); - deserializeInvoked.Should().BeFalse(); - fromCache.Should().Be(default(ResultPrimitive)); - } - - #endregion -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Caching/SlidingTtlSpecs.cs b/src/Polly.Specs/Caching/SlidingTtlSpecs.cs index 2bd32116264..4ee9038ee08 100644 --- a/src/Polly.Specs/Caching/SlidingTtlSpecs.cs +++ b/src/Polly.Specs/Caching/SlidingTtlSpecs.cs @@ -3,43 +3,44 @@ using Polly.Caching; using Xunit; -namespace Polly.Specs.Caching; - -public class SlidingTtlSpecs +namespace Polly.Specs.Caching { - [Fact] - public void Should_throw_when_timespan_is_less_than_zero() + public class SlidingTtlSpecs { - Action configure = () => new SlidingTtl(TimeSpan.FromMilliseconds(-1)); + [Fact] + public void Should_throw_when_timespan_is_less_than_zero() + { + Action configure = () => new SlidingTtl(TimeSpan.FromMilliseconds(-1)); - configure.Should().Throw().And.ParamName.Should().Be("slidingTtl"); - } + configure.Should().Throw().And.ParamName.Should().Be("slidingTtl"); + } - [Fact] - public void Should_not_throw_when_timespan_is_zero() - { - Action configure = () => new SlidingTtl(TimeSpan.Zero); + [Fact] + public void Should_not_throw_when_timespan_is_zero() + { + Action configure = () => new SlidingTtl(TimeSpan.Zero); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_allow_timespan_max_value() - { - Action configure = () => new SlidingTtl(TimeSpan.MaxValue); + [Fact] + public void Should_allow_timespan_max_value() + { + Action configure = () => new SlidingTtl(TimeSpan.MaxValue); - configure.Should().NotThrow(); - } + configure.Should().NotThrow(); + } - [Fact] - public void Should_return_configured_timespan() - { - var ttl = TimeSpan.FromSeconds(30); + [Fact] + public void Should_return_configured_timespan() + { + var ttl = TimeSpan.FromSeconds(30); - var ttlStrategy = new SlidingTtl(ttl); + var ttlStrategy = new SlidingTtl(ttl); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); - retrieved.Timespan.Should().Be(ttl); - retrieved.SlidingExpiration.Should().BeTrue(); + var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + retrieved.Timespan.Should().Be(ttl); + retrieved.SlidingExpiration.Should().BeTrue(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs index f1fe5bc5783..01dad079089 100644 --- a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs @@ -11,3098 +11,3099 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker; +namespace Polly.Specs.CircuitBreaker +{ + [Collection(Constants.SystemClockDependentTestCollection)] + public class AdvancedCircuitBreakerAsyncSpecs : IDisposable + { + #region Configuration tests + + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_if_failure_threshold_is_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } + + [Fact] + public void Should_throw_if_failure_threshold_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } + + [Fact] + public void Should_be_able_to_handle_a_failure_threshold_of_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + + action.Should().NotThrow(); + } + + [Fact] + public void Should_throw_if_failure_threshold_is_greater_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } + + [Fact] + public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync( + 0.5, + TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), + 4, + TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("samplingDuration"); + } + + [Fact] + public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); + + action.Should().NotThrow(); + } + + [Fact] + public void Should_throw_if_minimum_throughput_is_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } + + [Fact] + public void Should_throw_if_minimum_throughput_is_less_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } + + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } + + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); + + action.Should().NotThrow(); + } + + [Fact] + public void Should_initialise_to_closed_state() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #region Circuit-breaker threshold-to-break tests + + #region Tests that are independent from health metrics implementation + + // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of three actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // Failure threshold exceeded, but throughput threshold not yet. + + // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .Or() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw unhandled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #region With sample duration higher than 199 ms so that multiple windows are used + + // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice. + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. + + // Two actions at the start of the original timeslice. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Creates a new window right at the end of the original timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure opens the circuit, because it is the second failure of four calls + // equalling the failure threshold. The minimum threshold within the defined + // sampling duration is met, when using rolling windows. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Two of three actions in this test occur within the first timeslice. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // A third failure occurs just at the beginning of the new timeslice making + // the number of failures above the failure threshold. However, the throughput is + // below the minimum threshold as to open the circuit. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + var numberOfWindowsDefinedInCircuitBreaker = 10; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to the second window in the rolling metrics + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); + + // Three actions occur in the second window of the first timeslice + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + #endregion -[Collection(Constants.SystemClockDependentTestCollection)] -public class AdvancedCircuitBreakerAsyncSpecs : IDisposable -{ - #region Configuration tests + #region With sample duration at 199 ms so that only a single window is used - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); + // These tests on AdvancedCircuitBreakerAsync operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a 199ms period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_throw_if_failure_threshold_is_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_throw_if_failure_threshold_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_be_able_to_handle_a_failure_threshold_of_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - action.Should().NotThrow(); - } + } - [Fact] - public void Should_throw_if_failure_threshold_is_greater_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync( - 0.5, - TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), - 4, - TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("samplingDuration"); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - action.Should().NotThrow(); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_throw_if_minimum_throughput_is_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_throw_if_minimum_throughput_is_less_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); + } - action.Should().NotThrow(); - } + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_initialise_to_closed_state() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + var samplingDuration = TimeSpan.FromMilliseconds(199); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - #endregion + // Four of four actions in this test throw handled failures; but only the first within the timeslice. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region Circuit-breaker threshold-to-break tests + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region Tests that are independent from health metrics implementation + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of three actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // Failure threshold exceeded, but throughput threshold not yet. - - // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .Or() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw unhandled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + } - #endregion + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures; but only the first within the timeslice. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region With sample duration higher than 199 ms so that multiple windows are used + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Tests on the AdvancedCircuitBreakerAsync operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice. - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + } - } + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var samplingDuration = TimeSpan.FromMilliseconds(199); - } + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - } + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure does not open the circuit, because a new duration should have + // started and with such low sampling duration, windows should not be used. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #endregion + + #region Circuit-breaker open->half-open->open/closed tests + + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } + + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + var anotherWindowDuration = samplingDuration.Seconds / 2d + 1; + SystemClock.UtcNow = () => time.AddSeconds(anotherWindowDuration); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Since the call that opened the circuit occurred in a later window, then the + // break duration must be simulated as from that call. + SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherWindowDuration); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } + + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); - var samplingDuration = TimeSpan.FromSeconds(10); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // first call after duration raises an exception, so circuit should open again + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + + } + + [Fact] + public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - var samplingDuration = TimeSpan.FromSeconds(10); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - } + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - var samplingDuration = TimeSpan.FromSeconds(10); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. - // Two actions at the start of the original timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Creates a new window right at the end of the original timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. - SystemClock.UtcNow = () => time.Add(samplingDuration); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - } + // exceptions raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => + { + breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - [Fact] - public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Three of four actions in this test occur within the first timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // This failure opens the circuit, because it is the second failure of four calls - // equalling the failure threshold. The minimum threshold within the defined - // sampling duration is met, when using rolling windows. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Two of three actions in this test occur within the first timeslice. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // A third failure occurs just at the beginning of the new timeslice making - // the number of failures above the failure threshold. However, the throughput is - // below the minimum threshold as to open the circuit. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; - [Fact] - public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - var numberOfWindowsDefinedInCircuitBreaker = 10; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to the second window in the rolling metrics - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); - - // Three actions occur in the second window of the first timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - #endregion + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - #region With sample duration at 199 ms so that only a single window is used + Task secondExecution = Task.Factory.StartNew(async () => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // These tests on AdvancedCircuitBreakerAsync operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a 199ms period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + try + { + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + await TaskHelper.EmptyTask; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); - } + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } + } - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - } + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var samplingDuration = TimeSpan.FromMilliseconds(199); + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => + { + breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Four of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); + Task secondExecution = Task.Factory.StartNew(async () => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - } + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + await TaskHelper.EmptyTask; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - var samplingDuration = TimeSpan.FromMilliseconds(199); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + } + } - // Two of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #region Isolate and reset tests - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - } + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Three of four actions in this test occur within the first timeslice. - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // This failure does not open the circuit, because a new duration should have - // started and with such low sampling duration, windows should not be used. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().Throw(); - #endregion + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + } - #region Circuit-breaker open->half-open->open/closed tests + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromSeconds(30); - var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + #endregion - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #region State-change delegate tests - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - var anotherWindowDuration = samplingDuration.Seconds / 2d + 1; - SystemClock.UtcNow = () => time.AddSeconds(anotherWindowDuration); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Since the call that opened the circuit occurred in a later window, then the - // break duration must be simulated as from that call. - SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherWindowDuration); - - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + var durationOfBreak = TimeSpan.FromSeconds(30); + Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onResetCalled.Should().BeFalse(); + } - var durationOfBreak = TimeSpan.FromSeconds(30); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + onBreakCalled.Should().BeTrue(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onBreakCalled.Should().BeFalse(); - // first call after duration raises an exception, so circuit should open again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Isolate(); - } + onBreakCalled.Should().BeTrue(); + } - [Fact] - public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - var durationOfBreak = TimeSpan.FromSeconds(30); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + // call through circuit when already broken - should not retrigger onBreak + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + + using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + { + var longRunningExecution = Task.Factory.StartNew(() => + { + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Awaiting(x => x.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + permitMainThreadToOpenCircuit.Set(); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); - - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); - - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // OnActionPreExecute() should reject a second execution. (tho still in half-open condition). - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // Throw a further failure when rest of test has already broken the circuit. + breaker.CircuitState.Should().Be(CircuitState.Open); + throw new DivideByZeroException(); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - - // exceptions raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); - - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. - - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; - - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); + + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } + } - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + [Fact] + public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Task secondExecution = Task.Factory.StartNew(async () => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - try - { - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - } - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); - - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; - - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + onBreakCalled.Should().Be(1); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - Task secondExecution = Task.Factory.StartNew(async () => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - await breaker.ExecuteAsync(async () => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + onResetCalled.Should().BeFalse(); - await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); } - } - #endregion + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - #region Isolate and reset tests + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + onBreakCalled.Should().Be(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + onHalfOpenCalled.Should().Be(1); // called as action was placed for execution + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); // called after action succeeded + } - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); - - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - } + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - #endregion + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - #region State-change delegate tests + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromSeconds(30); - Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - onResetCalled.Should().BeFalse(); - } + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().BeTrue(); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - onBreakCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().Throw(); - breaker.Isolate(); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().NotThrow(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - // call through circuit when already broken - should not retrigger onBreak - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + #region Tests of supplied parameters to onBreak delegate - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) - { - var longRunningExecution = Task.Factory.StartNew(() => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; + + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - permitMainThreadToOpenCircuit.Set(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); + passedException?.Should().BeOfType(); + } - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + [Fact] + public void Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; + + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); - // Break circuit in the normal manner: onBreak() should be called once. - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); breaker.Awaiting(x => x.RaiseExceptionAsync()) .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // onBreak() should still only have been called once. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + + transitionedState?.Should().Be(CircuitState.Closed); } - } - [Fact] - public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); - - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + [Fact] + public void Should_call_onbreak_with_a_state_of_half_open() + { + var transitionedStates = new List(); + + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Open); - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - onHalfOpenCalled.Should().Be(1); // called as action was placed for execution - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); // called after action succeeded - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + // first call after duration raises an exception, so circuit should open again + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); - - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); - - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); - - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().NotThrow(); - } + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - #region Tests of supplied parameters to onBreak delegate + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; - - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - passedException?.Should().BeOfType(); - } + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - [Fact] - public void Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; - - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - transitionedState?.Should().Be(CircuitState.Closed); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() - { - var transitionedStates = new List(); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + passedBreakTimespan.Should().Be(durationOfBreak); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromMinutes(1), + onBreak: onBreak, + onReset: onReset + ); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.CircuitState.Should().Be(CircuitState.Open); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #endregion - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + #region Tests that supplied context is passed to stage-change delegates - // first call after duration raises an exception, so circuit should open again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; + + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + breaker.Awaiting(x => x.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + )).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + [Fact] + public async Task Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - passedBreakTimespan.Should().Be(durationOfBreak); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromMinutes(1), - onBreak: onBreak, - onReset: onReset - ); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - #region Tests that supplied context is passed to stage-change delegates + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; - - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public async Task Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + // first call after duration should invoke onReset, with context + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var durationOfBreak = TimeSpan.FromSeconds(30); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + contextData.Should().BeEmpty(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; + + Action onBreak = + (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = + context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // first call after duration should invoke onReset, with context - await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); + #endregion - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #region LastException property - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + breaker.LastException.Should().BeNull(); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + [Fact] + public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - contextData.Should().BeEmpty(); - } + breaker.LastException.Should().BeOfType(); + } - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; - - Action onBreak = - (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = - context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); - - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + [Fact] + public void Should_set_LastException_to_last_raised_exception_when_breaking() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - #endregion + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region LastException property + breaker.LastException.Should().BeOfType(); + } - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_set_LastException_to_null_on_circuit_reset() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.LastException.Should().BeOfType(); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.LastException.Should().BeOfType(); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + breaker.LastException.Should().BeOfType(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Reset(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastException.Should().BeNull(); + } - breaker.LastException.Should().BeOfType(); + #endregion - breaker.Reset(); + #region Cancellation support - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - #endregion + var cancellationTokenSource = new CancellationTokenSource(); - #region Cancellation support + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - attemptsInvoked.Should().Be(1); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + // Circuit is now broken. - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - // Circuit is now broken. + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - cancellationTokenSource.Cancel(); + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; - - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(0); - } - - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + var policyCancellationTokenSource = new CancellationTokenSource(); + var policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - implicitlyCapturedActionCancellationTokenSource.Cancel(); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + var attemptsInvoked = 0; - breaker.Awaiting(x => x.ExecuteAsync(async _ => + breaker.Awaiting(x => x.ExecuteAsync(async _ => { attemptsInvoked++; await TaskHelper.EmptyTask; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().NotThrow(); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + breaker.Awaiting(action) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + breaker.Awaiting(action) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs index 1168916f35d..c0855d4c65e 100644 --- a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs @@ -11,3077 +11,3079 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensions.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class AdvancedCircuitBreakerSpecs : IDisposable +namespace Polly.Specs.CircuitBreaker { - #region Configuration tests - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + [Collection(Constants.SystemClockDependentTestCollection)] + public class AdvancedCircuitBreakerSpecs : IDisposable { - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); + #region Configuration tests - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); - [Fact] - public void Should_throw_if_failure_threshold_is_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } + [Fact] + public void Should_throw_if_failure_threshold_is_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_throw_if_failure_threshold_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } + [Fact] + public void Should_throw_if_failure_threshold_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(-0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_be_able_to_handle_a_failure_threshold_of_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - action.Should().NotThrow(); - } + [Fact] + public void Should_be_able_to_handle_a_failure_threshold_of_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(1.0, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_throw_if_failure_threshold_is_greater_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + action.Should().NotThrow(); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("failureThreshold"); - } + [Fact] + public void Should_throw_if_failure_threshold_is_greater_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(1.01, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker( - 0.5, - TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), - 4, - TimeSpan.FromSeconds(30)); - - action.Should().Throw() - .And.ParamName.Should() - .Be("samplingDuration"); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("failureThreshold"); + } - [Fact] - public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); + [Fact] + public void Should_throw_if_timeslice_duration_is_less_than_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker( + 0.5, + TimeSpan.FromMilliseconds(20).Add(TimeSpan.FromTicks(-1)), + 4, + TimeSpan.FromSeconds(30)); + + action.Should().Throw() + .And.ParamName.Should() + .Be("samplingDuration"); + } - action.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_if_timeslice_duration_is_resolution_of_circuit() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromMilliseconds(20), 4, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_throw_if_minimum_throughput_is_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); + action.Should().NotThrow(); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } + [Fact] + public void Should_throw_if_minimum_throughput_is_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 1, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_throw_if_minimum_throughput_is_less_than_one() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("minimumThroughput"); - } + [Fact] + public void Should_throw_if_minimum_throughput_is_less_than_one() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 0, TimeSpan.FromSeconds(30)); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); + action.Should().Throw() + .And.ParamName.Should() + .Be("minimumThroughput"); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, -TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - action.Should().NotThrow(); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.Zero); - [Fact] - public void Should_initialise_to_closed_state() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + action.Should().NotThrow(); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_initialise_to_closed_state() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - #region Circuit-breaker threshold-to-break tests + #endregion - #region Tests that are independent from health metrics implementation + #region Circuit-breaker threshold-to-break tests - // Tests on the AdvancedCircuitBreaker operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + #region Tests that are independent from health metrics implementation - [Fact] - public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of three actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // Failure threshold exceeded, but throughput threshold not yet. - - // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + // Tests on the AdvancedCircuitBreaker operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - [Fact] - public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .Or() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw unhandled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_is_equalled_but_last_call_is_success() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of three actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // Failure threshold exceeded, but throughput threshold not yet. - #endregion + // Throughput threshold will be exceeded by the below successful call, but we never break on a successful call; hence don't break on this. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } - #region With sample duration higher than 199 ms so that multiple windows are used + [Fact] + public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_specified_exceptions() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .Or() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw unhandled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Tests on the AdvancedCircuitBreaker operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a ten-second period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice. - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + #endregion - } + #region With sample duration higher than 199 ms so that multiple windows are used - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Tests on the AdvancedCircuitBreaker operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a ten-second period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. - } + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var samplingDuration = TimeSpan.FromSeconds(10); + var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice. + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - } + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var samplingDuration = TimeSpan.FromSeconds(10); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / 2d + 1); - // Four of four actions in this test throw handled failures; but only the first three within the timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures; but only the first three within the timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. + + // Two actions at the start of the original timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Creates a new window right at the end of the original timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var samplingDuration = TimeSpan.FromSeconds(10); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure opens the circuit, because it is the second failure of four calls + // equalling the failure threshold. The minimum threshold within the defined + // sampling duration is met, when using rolling windows. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Two of three actions in this test occur within the first timeslice. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // A third failure occurs just at the beginning of the new timeslice making + // the number of failures above the failure threshold. However, the throughput is + // below the minimum threshold as to open the circuit. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromSeconds(10); + var numberOfWindowsDefinedInCircuitBreaker = 10; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to the second window in the rolling metrics + SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); + + // Three actions occur in the second window of the first timeslice + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + #endregion + + #region With sample duration at 199 ms so that only a single window is used + + // These tests on AdvancedCircuitBreaker operation typically use a breaker: + // - with a failure threshold of >=50%, + // - and a throughput threshold of 4 + // - across a 199ms period. + // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Three of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures; but only the first within the timeslice. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Two of four actions in this test throw handled failures; but only the first within the timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. + SystemClock.UtcNow = () => time.Add(samplingDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + } + + [Fact] + public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of three actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromMilliseconds(199), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // One of four actions in this test throw handled failures. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice + } + + [Fact] + public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var samplingDuration = TimeSpan.FromMilliseconds(199); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + // Executing a single invocation to ensure timeslice is created + // This invocation is not be counted against the threshold + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // The time is set to just at the end of the sampling duration ensuring + // the invocations are within the timeslice, but only barely. + SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + + // Three of four actions in this test occur within the first timeslice. + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => { })) + .Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Setting the time to just barely into the new timeslice + SystemClock.UtcNow = () => time.Add(samplingDuration); + + // This failure does not open the circuit, because a new duration should have + // started and with such low sampling duration, windows should not be used. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #endregion + + #region Circuit-breaker open->half-open->open/closed tests + + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + var samplingDuration = TimeSpan.FromSeconds(10); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: samplingDuration, + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later + // ensures that even if there are only two windows, then the invocations are placed in the second. + // They are still placed within same timeslice + var anotherwindowDuration = samplingDuration.Seconds / 2d + 1; + SystemClock.UtcNow = () => time.AddSeconds(anotherwindowDuration); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // Since the call that opened the circuit occurred in a later window, then the + // break duration must be simulated as from that call. + SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherwindowDuration); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // first call after duration raises an exception, so circuit should open again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + } - } + [Fact] + public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_error_occurring_just_at_the_end_of_the_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromSeconds(30); - var samplingDuration = TimeSpan.FromSeconds(10); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures; but only the first three within the original timeslice. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Two actions at the start of the original timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // Creates a new window right at the end of the original timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. If timeslice/window rollover is precisely defined, this should cause first two actions to be forgotten from statistics (rolled out of the window of relevance), and thus the circuit not to break. - SystemClock.UtcNow = () => time.Add(samplingDuration); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => {}); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - } + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromSeconds(30); - } + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Three of four actions in this test occur within the first timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // This failure opens the circuit, because it is the second failure of four calls - // equalling the failure threshold. The minimum threshold within the defined - // sampling duration is met, when using rolling windows. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_failures_in_beginning_of_new_timeslice_when_below_minimum_throughput_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Two of three actions in this test occur within the first timeslice. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // A third failure occurs just at the beginning of the new timeslice making - // the number of failures above the failure threshold. However, the throughput is - // below the minimum threshold as to open the circuit. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - [Fact] - public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_and_failures_in_first_window_in_next_timeslice_exceeds_failure_threshold_and_minimum_threshold() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromSeconds(10); - var numberOfWindowsDefinedInCircuitBreaker = 10; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to the second window in the rolling metrics - SystemClock.UtcNow = () => time.AddSeconds(samplingDuration.Seconds / (double)numberOfWindowsDefinedInCircuitBreaker); - - // Three actions occur in the second window of the first timeslice - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - #endregion + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - #region With sample duration at 199 ms so that only a single window is used + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // These tests on AdvancedCircuitBreaker operation typically use a breaker: - // - with a failure threshold of >=50%, - // - and a throughput threshold of 4 - // - across a 199ms period. - // These provide easy values for testing for failure and throughput thresholds each being met and non-met, in combination. + var durationOfBreak = TimeSpan.FromSeconds(30); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_exceeded_though_not_all_are_failures_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Three of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - } + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Two of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var samplingDuration = TimeSpan.FromMilliseconds(199); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // Four of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromSeconds(30); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - // Adjust SystemClock so that timeslice (clearly) expires; fourth exception thrown in next-recorded timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration).Add(samplingDuration); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // exceptions raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - } + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput_threshold_not_met_before_timeslice_expires_even_if_timeslice_expires_only_exactly_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - var samplingDuration = TimeSpan.FromMilliseconds(199); + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Two of four actions in this test throw handled failures; but only the first within the timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - // Adjust SystemClock so that timeslice (just) expires; fourth exception thrown in following timeslice. - SystemClock.UtcNow = () => time.Add(samplingDuration); + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + try + { + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - [Fact] - public void Should_open_circuit_with_the_last_raised_exception_if_failure_threshold_equalled_and_throughput_threshold_equalled_even_if_only_just_within_timeslice_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Adjust SystemClock so that timeslice doesn't quite expire; fourth exception thrown in same timeslice. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); - } + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } + } - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_threshold_not_met_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of three actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_threshold_met_before_timeslice_expires_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromMilliseconds(199), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // One of four actions in this test throw handled failures. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - // No adjustment to SystemClock.UtcNow, so all exceptions were raised within same timeslice - } + var durationOfBreak = TimeSpan.FromSeconds(30); - [Fact] - public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_failure_threshold_and_failures_in_beginning_of_new_timeslice_where_total_equals_failure_threshold_low_sampling_duration() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var samplingDuration = TimeSpan.FromMilliseconds(199); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - // Executing a single invocation to ensure timeslice is created - // This invocation is not be counted against the threshold - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // The time is set to just at the end of the sampling duration ensuring - // the invocations are within the timeslice, but only barely. - SystemClock.UtcNow = () => time.AddTicks(samplingDuration.Ticks - 1); - - // Three of four actions in this test occur within the first timeslice. - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.Execute(() => { })) - .Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Setting the time to just barely into the new timeslice - SystemClock.UtcNow = () => time.Add(samplingDuration); - - // This failure does not open the circuit, because a new duration should have - // started and with such low sampling duration, windows should not be used. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: durationOfBreak + ); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - #endregion + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Circuit-breaker open->half-open->open/closed tests + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; + + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_same_window() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - var durationOfBreak = TimeSpan.FromSeconds(30); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); - breaker.CircuitState.Should().Be(CircuitState.Open); + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + } + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #endregion - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + #region Isolate and reset tests - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with_failures_in_different_windows() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - var samplingDuration = TimeSpan.FromSeconds(10); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: samplingDuration, - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // Placing the rest of the invocations ('samplingDuration' / 2) + 1 seconds later - // ensures that even if there are only two windows, then the invocations are placed in the second. - // They are still placed within same timeslice - var anotherwindowDuration = samplingDuration.Seconds / 2d + 1; - SystemClock.UtcNow = () => time.AddSeconds(anotherwindowDuration); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // Since the call that opened the circuit occurred in a later window, then the - // break duration must be simulated as from that call. - SystemClock.UtcNow = () => time.Add(durationOfBreak).AddSeconds(anotherwindowDuration); - - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - var durationOfBreak = TimeSpan.FromSeconds(30); + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - // first call after duration raises an exception, so circuit should open again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - } + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromSeconds(30); - var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak + ); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + #endregion - SystemClock.UtcNow = () => time.Add(durationOfBreak); + #region State-change delegate tests - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - // first call after duration is successful, so circuit should reset - breaker.Execute(() => {}); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var durationOfBreak = TimeSpan.FromSeconds(30); + Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onResetCalled.Should().BeFalse(); + } - var durationOfBreak = TimeSpan.FromSeconds(30); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().BeFalse(); - var durationOfBreak = TimeSpan.FromSeconds(30); + breaker.Isolate(); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); + onBreakCalled.Should().BeTrue(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onBreakCalled.Should().Be(1); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // call through circuit when already broken - should not retrigger onBreak + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - - // exceptions raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); - - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. - - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; - - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - breaker.Invoking(x => x.Execute(() => + var longRunningExecution = Task.Factory.StartNew(() => { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); - - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + breaker.Invoking(x => x.Execute(() => + { + permitMainThreadToOpenCircuit.Set(); - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). + permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Throw a further failure when rest of test has already broken the circuit. + breaker.CircuitState.Should().Be(CircuitState.Open); + throw new DivideByZeroException(); - var secondExecution = Task.Factory.StartNew(() => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - try - { - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); + + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } } - } - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: durationOfBreak - ); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); - - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; - - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + [Fact] + public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var secondExecution = Task.Factory.StartNew(() => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onBreakCalled.Should().Be(1); - try - { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.Execute(() => - { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); } - } - #endregion + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - #region Isolate and reset tests + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + onResetCalled.Should().BeFalse(); - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); - - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #endregion + onBreakCalled.Should().Be(1); - #region State-change delegate tests + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + onHalfOpenCalled.Should().Be(1); // called as action was placed for execution + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); // called after action succeeded + } + + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - var durationOfBreak = TimeSpan.FromSeconds(30); - Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - onResetCalled.Should().BeFalse(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().BeTrue(); - } + // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - onBreakCalled.Should().BeFalse(); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; - breaker.Isolate(); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - onBreakCalled.Should().BeTrue(); - } + var durationOfBreak = TimeSpan.FromSeconds(30); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - // call through circuit when already broken - should not retrigger onBreak - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) - { - var longRunningExecution = Task.Factory.StartNew(() => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); - breaker.Invoking(x => x.Execute(() => - { - permitMainThreadToOpenCircuit.Set(); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). - permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - // Throw a further failure when rest of test has already broken the circuit. - breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); + #region Tests of supplied parameters to onBreak delegate - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; + + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Break circuit in the normal manner: onBreak() should be called once. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.Invoking(x => x.RaiseException()) .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); - - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + passedException?.Should().BeOfType(); } - } - - [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); - - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; + + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - onHalfOpenCalled.Should().Be(1); // called as action was placed for execution - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); // called after action succeeded - } + transitionedState?.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); - - // No adjustment to SystemClock.UtcNow, so all exceptions raised within same timeslice - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + [Fact] + public void Should_call_onbreak_with_a_state_of_half_open() + { + var transitionedStates = new List(); + + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset, + onHalfOpen: onHalfOpen + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + // first call after duration raises an exception, so circuit should open again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Tests of supplied parameters to onBreak delegate + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; - - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - passedException?.Should().BeOfType(); - } + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - [Fact] - public void Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; - - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - transitionedState?.Should().Be(CircuitState.Closed); - } + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() - { - var transitionedStates = new List(); + var durationOfBreak = TimeSpan.FromSeconds(30); - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset, - onHalfOpen: onHalfOpen - ); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + passedBreakTimespan.Should().Be(durationOfBreak); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.CircuitState.Should().Be(CircuitState.Open); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + #endregion - // first call after duration raises an exception, so circuit should open again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Tests that supplied context is passed to stage-change delegates - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; + + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + breaker.Invoking(x => x.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + )).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromSeconds(30); - passedBreakTimespan.Should().Be(durationOfBreak); - } + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region Tests that supplied context is passed to stage-change delegates + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; - - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // first call after duration should invoke onReset, with context + breaker.Execute(_ => { }, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromSeconds(30); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: TimeSpan.FromSeconds(30), + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should().BeEmpty(); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; + + Action onBreak = + (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = + context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromSeconds(30); + + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 4, + durationOfBreak: durationOfBreak, + onBreak: onBreak, + onReset: onReset + ); + + // Four of four actions in this test throw handled failures. + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // first call after duration should invoke onReset, with context - breaker.Execute(_ => { }, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + breaker.Invoking(x => x.RaiseException(new { key = "original_value" }.AsDictionary())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // first call after duration is successful, so circuit should reset + breaker.Execute(_ => { }, new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: TimeSpan.FromSeconds(30), - onBreak: onBreak, - onReset: onReset - ); + #endregion - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + #region LastException property - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); + + breaker.LastException.Should().BeNull(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - contextData.Should().BeEmpty(); - } + breaker.LastException.Should().BeOfType(); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; - - Action onBreak = - (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = - context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromSeconds(30); - - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 4, - durationOfBreak: durationOfBreak, - onBreak: onBreak, - onReset: onReset - ); - - // Four of four actions in this test throw handled failures. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException(new { key = "original_value" }.AsDictionary())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - - // first call after duration is successful, so circuit should reset - breaker.Execute(_ => { }, new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + [Fact] + public void Should_set_LastException_to_last_raised_exception_when_breaking() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region LastException property + breaker.LastException.Should().BeOfType(); + } - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_set_LastException_to_null_on_circuit_reset() + { + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker( + failureThreshold: 0.5, + samplingDuration: TimeSpan.FromSeconds(10), + minimumThroughput: 2, + durationOfBreak: TimeSpan.FromSeconds(30) + ); - [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.LastException.Should().BeOfType(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.LastException.Should().BeOfType(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() - { - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker( - failureThreshold: 0.5, - samplingDuration: TimeSpan.FromSeconds(10), - minimumThroughput: 2, - durationOfBreak: TimeSpan.FromSeconds(30) - ); + breaker.LastException.Should().BeOfType(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Reset(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastException.Should().BeNull(); + } - breaker.LastException.Should().BeOfType(); + #endregion - breaker.Reset(); + #region Cancellation support - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - #endregion + var cancellationTokenSource = new CancellationTokenSource(); - #region Cancellation support + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - attemptsInvoked.Should().Be(1); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + // Circuit is now broken. - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - // Circuit is now broken. + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - cancellationTokenSource.Cancel(); + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; - - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(0); - } - - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + var policyCancellationTokenSource = new CancellationTokenSource(); + var policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - implicitlyCapturedActionCancellationTokenSource.Cancel(); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + var attemptsInvoked = 0; - breaker.Invoking(x => x.Execute(_ => + breaker.Invoking(x => x.Execute(_ => { attemptsInvoked++; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs index 30d6ce0e0f1..0457ad3ad73 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs @@ -11,1685 +11,1686 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CircuitBreakerAsyncSpecs : IDisposable +namespace Polly.Specs.CircuitBreaker { - #region Configuration tests - - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CircuitBreakerAsyncSpecs : IDisposable { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.MaxValue); + #region Configuration tests - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.MaxValue); - [Fact] - public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .Handle() - .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("exceptionsAllowedBeforeBreaking"); - } + [Fact] + public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .Handle() + .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); + action.Should().Throw() + .And.ParamName.Should() + .Be("exceptionsAllowedBeforeBreaking"); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - action.Should().NotThrow(); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - #region Circuit-breaker threshold-to-break tests + #endregion - [Fact] - public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + #region Circuit-breaker threshold-to-break tests - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(b => b.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Awaiting(b => b.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - } + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() - { - var breaker = Policy - .Handle() - .Or() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() + { + var breaker = Policy + .Handle() + .Or() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() - { - var breaker = Policy - .Handle() - .Or() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Circuit-breaker open->half-open->open/closed tests + // 2 exception raised, circuit is now open + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() + { + var breaker = Policy + .Handle() + .Or() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + #endregion - var durationOfBreak = TimeSpan.FromMinutes(1); + #region Circuit-breaker open->half-open->open/closed tests - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // circuit has been reset so should once again allow 2 exceptions to be raised before breaking - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // circuit has been reset so should once again allow 2 exceptions to be raised before breaking + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } + + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } + + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - Task secondExecution = Task.Factory.StartNew(async () => + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - try + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => { - await breaker.ExecuteAsync(async () => + breaker.Awaiting(x => x.ExecuteAsync(async () => { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); + + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) + firstExecutionActive = false; + + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); + + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + + Task secondExecution = Task.Factory.StartNew(async () => { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + try + { + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + await TaskHelper.EmptyTask; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Release first execution soon as second overlapping execution is done gathering data. + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } } - } - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; - - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) { - breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => + { + breaker.Awaiting(x => x.ExecuteAsync(async () => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + await TaskHelper.EmptyTask; + firstExecutionActive = false; + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - Task secondExecution = Task.Factory.StartNew(async () => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - try + Task secondExecution = Task.Factory.StartNew(async () => { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - await breaker.ExecuteAsync(async () => + try { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - await TaskHelper.EmptyTask; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + + await TaskHelper.EmptyTask; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // Release first execution soon as second overlapping execution is done gathering data. + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + } } - } - #endregion + #endregion - #region Isolate and reset tests + #region Isolate and reset tests - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().Throw(); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); - } + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + } - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); - } + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + } - #endregion + #endregion - #region State-change delegate tests + #region State-change delegate tests - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onResetCalled.Should().BeFalse(); - } + onResetCalled.Should().BeFalse(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - breaker.Isolate(); + breaker.Isolate(); - onBreakCalled.Should().BeTrue(); - } + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - // call through circuit when already broken - should not retrigger onBreak - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + // call through circuit when already broken - should not retrigger onBreak + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var longRunningExecution = Task.Factory.StartNew(() => + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Awaiting(x => x.ExecuteAsync(async () => + var longRunningExecution = Task.Factory.StartNew(() => { - await TaskHelper.EmptyTask; + breaker.CircuitState.Should().Be(CircuitState.Closed); - permitMainThreadToOpenCircuit.Set(); + breaker.Awaiting(x => x.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; + + permitMainThreadToOpenCircuit.Set(); // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); // Throw a further failure when rest of test has already broken the circuit. breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); + throw new DivideByZeroException(); - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); + })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - // Break circuit in the normal manner: onBreak() should be called once. - breaker.CircuitState.Should().Be(CircuitState.Closed); + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); + + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } + } + + [Fact] + public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + + onBreakCalled.Should().Be(0); + + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); onBreakCalled.Should().Be(0); + breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + .Should().Throw(); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // first call after duration is successful, so circuit should reset + breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); } - } - [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + onResetCalled.Should().BeFalse(); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - onBreakCalled.Should().Be(0); + breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - onBreakCalled.Should().Be(1); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + onBreakCalled.Should().Be(0); - // first call after duration is successful, so circuit should reset - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + onBreakCalled.Should().Be(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - onResetCalled.Should().BeFalse(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + // first call after duration is successful, so circuit should reset + breaker.ExecuteAsync(() => TaskHelper.EmptyTask); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - - // first call after duration is successful, so circuit should reset - breaker.ExecuteAsync(() => TaskHelper.EmptyTask); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - onBreakCalled.Should().Be(0); - - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().Be(0); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + onBreakCalled.Should().Be(0); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + onBreakCalled.Should().Be(1); - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) - .Should().Throw(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #region Tests of supplied parameters to onBreak delegate + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)) + .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => TaskHelper.EmptyTask)).Should().NotThrow(); + } - passedException?.Should().BeOfType(); - } + #region Tests of supplied parameters to onBreak delegate - [Fact] - public void Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - transitionedState?.Should().Be(CircuitState.Closed); - } + passedException?.Should().BeOfType(); + } - [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() - { - var transitionedStates = new List(); + [Fact] + public void Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + transitionedState?.Should().Be(CircuitState.Closed); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_call_onbreak_with_a_state_of_half_open() + { + var transitionedStates = new List(); - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + var durationOfBreak = TimeSpan.FromMinutes(1); - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - [Fact] - public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() - { - Exception passedException = null; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - var breaker = Policy - .HandleInner() - .Or() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + [Fact] + public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() + { + Exception passedException = null; - breaker.CircuitState.Should().Be(CircuitState.Open); + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - passedException?.Should().BeSameAs(toRaiseAsInner); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + var breaker = Policy + .HandleInner() + .Or() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) + .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + passedException?.Should().BeSameAs(toRaiseAsInner); + } - passedBreakTimespan.Should().Be(durationOfBreak); - } + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #endregion + passedBreakTimespan.Should().Be(durationOfBreak); + } - #region Tests that supplied context is passed to stage-change delegates + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - breaker.Awaiting(x => x.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - )).Should().Throw(); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Tests that supplied context is passed to stage-change delegates - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - [Fact] - public async Task Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Awaiting(x => x.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + )).Should().Throw(); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - // first call after duration should invoke onReset, with context - await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + var durationOfBreak = TimeSpan.FromMinutes(1); - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + // first call after duration should invoke onReset, with context + await breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - contextData.Should().BeEmpty(); - } + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + contextData.Should().BeEmpty(); + } - // 2 exception raised, circuit is now open - breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - SystemClock.UtcNow = () => time.Add(durationOfBreak); + Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - // first call after duration is successful, so circuit should reset - breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #endregion + var durationOfBreak = TimeSpan.FromMinutes(1); - #endregion + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - #region LastException property + // 2 exception raised, circuit is now open + breaker.Awaiting(x => x.RaiseExceptionAsync(new { key = "original_value" }.AsDictionary())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.LastException.Should().BeNull(); - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + // first call after duration is successful, so circuit should reset + breaker.ExecuteAsync(_ => TaskHelper.EmptyTask, new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.LastException.Should().BeOfType(); - } + #region LastException property - [Fact] - public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() - { - var breaker = Policy - .HandleInner() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + breaker.LastException.Should().BeNull(); + } - breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + [Fact] + public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.LastException.Should().BeSameAs(toRaiseAsInner); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.LastException.Should().BeOfType(); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + [Fact] + public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() + { + var breaker = Policy + .HandleInner() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync(withInner)) + .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); - breaker.LastException.Should().BeOfType(); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.LastException.Should().BeSameAs(toRaiseAsInner); + } - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + [Fact] + public void Should_set_LastException_to_last_raised_exception_when_breaking() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.LastException.Should().BeOfType(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Reset(); + breaker.LastException.Should().BeOfType(); + } - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_set_LastException_to_null_on_circuit_reset() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - #endregion + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - #region Cancellation support + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Open); - var cancellationTokenSource = new CancellationTokenSource(); + breaker.LastException.Should().BeOfType(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Reset(); - var scenario = new Scenario + breaker.LastException.Should().BeNull(); + } + + #endregion + + #region Cancellation support + + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; + + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - breaker.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - // Circuit is now broken. + breaker.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + // Circuit is now broken. - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - cancellationTokenSource.Cancel(); + cancellationTokenSource.Cancel(); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + breaker.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); - } + attemptsInvoked.Should().Be(0); + } - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + var policyCancellationTokenSource = new CancellationTokenSource(); + var policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - implicitlyCapturedActionCancellationTokenSource.Cancel(); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + var attemptsInvoked = 0; - breaker.Awaiting(x => x.ExecuteAsync(async _ => + breaker.Awaiting(x => x.ExecuteAsync(async _ => { attemptsInvoked++; await TaskHelper.EmptyTask; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, durationOfBreak); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().NotThrow(); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + breaker.Awaiting(action) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var breaker = Policy - .Handle() - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var breaker = Policy + .Handle() + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - breaker.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + breaker.Awaiting(action) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs index e93546836d1..8701d795729 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs @@ -9,1702 +9,1703 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.CircuitBreaker; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CircuitBreakerSpecs : IDisposable +namespace Polly.Specs.CircuitBreaker { - #region Configuration tests - - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CircuitBreakerSpecs : IDisposable { - var breaker = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.MaxValue); + #region Configuration tests - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.MaxValue); - [Fact] - public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .Handle() - .CircuitBreaker(0, TimeSpan.FromSeconds(10)); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("exceptionsAllowedBeforeBreaking"); - } + [Fact] + public void Should_throw_if_exceptions_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .Handle() + .CircuitBreaker(0, TimeSpan.FromSeconds(10)); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); + action.Should().Throw() + .And.ParamName.Should() + .Be("exceptionsAllowedBeforeBreaking"); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - action.Should().NotThrow(); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - #region Circuit-breaker threshold-to-break tests + #endregion - [Fact] - public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + #region Circuit-breaker threshold-to-break tests - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.Invoking(b => b.Execute(() =>{})).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Invoking(b => b.Execute(() =>{})).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() - { - var breaker = Policy - .Handle() - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - breaker.CircuitState.Should().Be(CircuitState.Open); - delegateExecutedWhenBroken.Should().BeFalse(); - } + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() + { + var breaker = Policy + .Handle() + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() - { - var breaker = Policy - .Handle() - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region Circuit-breaker open->half-open->open/closed tests + // 2 exception raised, circuit is now open + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + breaker.CircuitState.Should().Be(CircuitState.Open); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() + { + var breaker = Policy + .Handle() + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + #endregion - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + #region Circuit-breaker open->half-open->open/closed tests + + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - } + } - [Fact] - public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_raise_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => {}); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => {}); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // circuit has been reset so should once again allow 2 exceptions to be raised before breaking + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // circuit has been reset so should once again allow 2 exceptions to be raised before breaking - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should reject a second execution in the same time window. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should reject a second execution in the same time window. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); - - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. - - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; - - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - var secondExecution = Task.Factory.StartNew(() => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + { + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - try + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => { - breaker.Execute(() => + breaker.Invoking(x => x.Execute(() => { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - } - } + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(1, durationOfBreak); + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var secondExecution = Task.Factory.StartNew(() => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + try + { + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + + } + } - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(1, durationOfBreak); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var secondExecution = Task.Factory.StartNew(() => + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - try + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - - breaker.Execute(() => + breaker.Invoking(x => x.Execute(() => { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - }); - } - catch (BrokenCircuitException) + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); + + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; + + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); + + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + + var secondExecution = Task.Factory.StartNew(() => { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Release first execution soon as second overlapping execution is done gathering data. + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } + + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] {firstExecution, secondExecution}, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + Task.WaitAll(new[] {firstExecution, secondExecution}, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + } } - } - #endregion + #endregion - #region Isolate and reset tests + #region Isolate and reset tests - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - - // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + + // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - #region State-change delegate tests + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + #endregion - onResetCalled.Should().BeFalse(); - } + #region State-change delegate tests - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + onResetCalled.Should().BeFalse(); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - var onBreakCalled = false; - Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - onBreakCalled.Should().BeTrue(); - } + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + var onBreakCalled = false; + Action onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.Isolate(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + onBreakCalled.Should().BeTrue(); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // call through circuit when already broken - should not retrigger onBreak - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - var onBreakCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var breaker = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) - { - var longRunningExecution = Task.Factory.StartNew(() => - { - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // call through circuit when already broken - should not retrigger onBreak + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - breaker.Invoking(x => x.Execute(() => + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() + { + var onBreakCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var breaker = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + { + var longRunningExecution = Task.Factory.StartNew(() => { - permitMainThreadToOpenCircuit.Set(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(x => x.Execute(() => + { + permitMainThreadToOpenCircuit.Set(); // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); // Throw a further failure when rest of test has already broken the circuit. breaker.CircuitState.Should().Be(CircuitState.Open); - throw new DivideByZeroException(); + throw new DivideByZeroException(); - })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. - }, TaskCreationOptions.LongRunning); + })).Should().Throw(); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original exception will still be thrown. + }, TaskCreationOptions.LongRunning); - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); + + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } + } + + [Fact] + public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - // Break circuit in the normal manner: onBreak() should be called once. - breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); + breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + .Should().Throw(); + onBreakCalled.Should().Be(0); + + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); } - } - [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + onResetCalled.Should().BeFalse(); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - onBreakCalled.Should().Be(0); + breaker.Execute(() => { }); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(1); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + onBreakCalled.Should().Be(0); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - onResetCalled.Should().BeFalse(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => { }); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - breaker.Execute(() => { }); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - - // first call after duration is successful, so circuit should reset - breaker.Execute(() => { }); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(0); - - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().Be(0); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(0); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => { })) - .Should().Throw(); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); - } + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - #region Tests of supplied parameters to onBreak delegate + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception passedException = null; + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => { })) + .Should().Throw(); + + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => { })).Should().NotThrow(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + #region Tests of supplied parameters to onBreak delegate - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception passedException = null; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - passedException?.Should().BeOfType(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onbreak_with_a_state_of_closed() - { - CircuitState? transitionedState = null; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - Action onBreak = (_, state, _, _) => { transitionedState = state; }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; + breaker.CircuitState.Should().Be(CircuitState.Open); - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + passedException?.Should().BeOfType(); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_with_a_state_of_closed() + { + CircuitState? transitionedState = null; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + Action onBreak = (_, state, _, _) => { transitionedState = state; }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - transitionedState?.Should().Be(CircuitState.Closed); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onbreak_with_a_state_of_half_open() - { - var transitionedStates = new List(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; - Action onReset = _ => { }; - var onHalfOpen = () => { }; + breaker.CircuitState.Should().Be(CircuitState.Open); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + transitionedState?.Should().Be(CircuitState.Closed); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_call_onbreak_with_a_state_of_half_open() + { + var transitionedStates = new List(); - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); + Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; + Action onReset = _ => { }; + var onHalfOpen = () => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration raises an exception, so circuit should break again - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - transitionedStates[0].Should().Be(CircuitState.Closed); - transitionedStates[1].Should().Be(CircuitState.HalfOpen); - } + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() - { - Exception passedException = null; + SystemClock.UtcNow = () => time.Add(durationOfBreak); - Action onBreak = (exception, _, _) => { passedException = exception; }; - Action onReset = _ => { }; + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration raises an exception, so circuit should break again + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - var durationOfBreak = TimeSpan.FromMinutes(1); + transitionedStates[0].Should().Be(CircuitState.Closed); + transitionedStates[1].Should().Be(CircuitState.HalfOpen); + } - var breaker = Policy - .HandleInner() - .Or() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + [Fact] + public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwrapped_if_matched_as_inner() + { + Exception passedException = null; - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + Action onBreak = (exception, _, _) => { passedException = exception; }; + Action onReset = _ => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.Invoking(x => x.RaiseException(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + var breaker = Policy + .HandleInner() + .Or() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Open); + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - passedException?.Should().BeSameAs(toRaiseAsInner); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + breaker.Invoking(x => x.RaiseException(withInner)) + .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + passedException?.Should().BeSameAs(toRaiseAsInner); + } - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - passedBreakTimespan.Should().Be(durationOfBreak); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); + passedBreakTimespan.Should().Be(durationOfBreak); + } - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - #endregion + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region Tests that supplied context is passed to stage-change delegates + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + #endregion - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + #region Tests that supplied context is passed to stage-change delegates - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - breaker.Invoking(x => x.RaiseException( - new {key1 = "value1", key2 = "value2"}.AsDictionary() - )).Should().Throw(); + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + breaker.Invoking(x => x.RaiseException( + new {key1 = "value1", key2 = "value2"}.AsDictionary() + )).Should().Throw(); - Action onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + breaker.CircuitState.Should().Be(CircuitState.Open); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + Action onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + var durationOfBreak = TimeSpan.FromMinutes(1); - // first call after duration should invoke onReset, with context - breaker.Execute(_ => { }, new {key1 = "value1", key2 = "value2"}.AsDictionary()); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - var contextData = new {key1 = "value1", key2 = "value2"}.AsDictionary(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - Action onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + // first call after duration should invoke onReset, with context + breaker.Execute(_ => { }, new {key1 = "value1", key2 = "value2"}.AsDictionary()); - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + var contextData = new {key1 = "value1", key2 = "value2"}.AsDictionary(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + Action onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - contextData.Should().BeEmpty(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + breaker.CircuitState.Should().Be(CircuitState.Open); - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + contextData.Should().BeEmpty(); + } - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - var durationOfBreak = TimeSpan.FromMinutes(1); + Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.RaiseException(new {key = "original_value"}.AsDictionary())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - // first call after duration is successful, so circuit should reset - breaker.Execute(_ => { }, new {key = "new_value"}.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.RaiseException(new {key = "original_value"}.AsDictionary())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - #endregion + SystemClock.UtcNow = () => time.Add(durationOfBreak); - #endregion + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - #region LastException property + // first call after duration is successful, so circuit should reset + breaker.Execute(_ => { }, new {key = "new_value"}.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_initialise_LastException_to_null_on_creation() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + #endregion - breaker.LastException.Should().BeNull(); - } + #endregion - [Fact] - public void Should_set_LastException_on_handling_exception_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + #region LastException property - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_initialise_LastException_to_null_on_creation() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.LastException.Should().BeNull(); + } - breaker.LastException.Should().BeOfType(); - } + [Fact] + public void Should_set_LastException_on_handling_exception_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() - { - var breaker = Policy - .HandleInner() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + breaker.LastException.Should().BeOfType(); + } - breaker.Invoking(x => x.RaiseException(withInner)) - .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); + [Fact] + public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() + { + var breaker = Policy + .HandleInner() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.LastException.Should().BeSameAs(toRaiseAsInner); - } + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - [Fact] - public void Should_set_LastException_to_last_raised_exception_when_breaking() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(x => x.RaiseException(withInner)) + .Should().Throw().Which.Should().BeSameAs(toRaiseAsInner); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.LastException.Should().BeSameAs(toRaiseAsInner); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_set_LastException_to_last_raised_exception_when_breaking() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.LastException.Should().BeOfType(); - } + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_set_LastException_to_null_on_circuit_reset() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.LastException.Should().BeOfType(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_set_LastException_to_null_on_circuit_reset() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.LastException.Should().BeOfType(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.Reset(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - breaker.LastException.Should().BeNull(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); - #endregion + breaker.LastException.Should().BeOfType(); - #region ExecuteAndCapture with HandleInner + breaker.Reset(); - [Fact] - public void Should_set_PolicyResult_on_handling_inner_exception() - { - var breaker = Policy - .HandleInner() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.LastException.Should().BeNull(); + } + #endregion - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + #region ExecuteAndCapture with HandleInner - var policyResult = breaker.ExecuteAndCapture(() => throw withInner); + [Fact] + public void Should_set_PolicyResult_on_handling_inner_exception() + { + var breaker = Policy + .HandleInner() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - policyResult.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - policyResult.FinalException.Should().BeSameAs(toRaiseAsInner); - } - #endregion + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - #region Cancellation support + var policyResult = breaker.ExecuteAndCapture(() => throw withInner); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + policyResult.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + policyResult.FinalException.Should().BeSameAs(toRaiseAsInner); + } - var cancellationTokenSource = new CancellationTokenSource(); + #endregion - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation support - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var breaker = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.FromMinutes(1)); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - breaker.Invoking(x => x.RaiseException()) - .Should().Throw(); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - breaker.Invoking(x => x.RaiseException()) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - // Circuit is now broken. + attemptsInvoked.Should().Be(1); + } - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var breaker = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.FromMinutes(1)); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Invoking(x => x.RaiseException()) + .Should().Throw(); - cancellationTokenSource.Cancel(); + breaker.Invoking(x => x.RaiseException()) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + // Circuit is now broken. - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + cancellationTokenSource.Cancel(); - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + breaker.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + attemptsInvoked.Should().Be(0); + } - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); + + var policyCancellationTokenSource = new CancellationTokenSource(); + var policyCancellationToken = policyCancellationTokenSource.Token; + + var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - implicitlyCapturedActionCancellationTokenSource.Cancel(); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + var attemptsInvoked = 0; - breaker.Invoking(x => x.Execute(_ => + breaker.Invoking(x => x.Execute(_ => { attemptsInvoked++; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .Handle() - .CircuitBreaker(2, durationOfBreak); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .Handle() + .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + breaker.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs index 62158388007..8e322a92a24 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs @@ -11,1594 +11,1595 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CircuitBreakerTResultAsyncSpecs : IDisposable +namespace Polly.Specs.CircuitBreaker { - #region Configuration tests - - [Fact] - public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CircuitBreakerTResultAsyncSpecs : IDisposable { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.MaxValue); + #region Configuration tests - var result = await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault); - result.Should().Be(ResultPrimitive.Fault); - } + [Fact] + public async Task Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.MaxValue); - [Fact] - public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); + var result = await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault); + result.Should().Be(ResultPrimitive.Fault); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("handledEventsAllowedBeforeBreaking"); - } + [Fact] + public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(0, TimeSpan.FromSeconds(10)); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); + action.Should().Throw() + .And.ParamName.Should() + .Be("handledEventsAllowedBeforeBreaking"); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, -TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.Zero); - action.Should().NotThrow(); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - #region Circuit-breaker threshold-to-break tests + #endregion - [Fact] - public async Task Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + #region Circuit-breaker threshold-to-break tests - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.Fault); - [Fact] - public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + [Fact] + public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(b => b.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Good))) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public async Task Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public async Task Should_not_open_circuit_if_result_returned_is_not_the_handled_result() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault))) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(b => b.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Good))) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public async Task Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultYetAgain) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public async Task Should_not_open_circuit_if_result_returned_is_not_the_handled_result() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public async Task Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public async Task Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultYetAgain) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - [Fact] - public async Task Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region Circuit-breaker open->half-open->open/closed tests + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - var durationOfBreak = TimeSpan.FromMinutes(1); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - } + (await breaker.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain))) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + #endregion - var durationOfBreak = TimeSpan.FromMinutes(1); + #region Circuit-breaker open->half-open->open/closed tests - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + [Fact] + public async Task Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // first call after duration returns a fault, so circuit should break again - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] - public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public async Task Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // circuit has been reset so should once again allow 2 faults to be raised before breaking - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + // first call after duration returns a fault, so circuit should break again + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw(); + } - breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public async Task Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // circuit has been reset so should once again allow 2 faults to be raised before breaking + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); + breaker.Awaiting(b => b.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - [Fact] - public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + [Fact] + public async Task Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - await TaskHelper.EmptyTask; - firstExecutionActive = false; + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - return ResultPrimitive.Good; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - Task secondExecution = Task.Factory.StartNew(async () => + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - try + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => { - await breaker.ExecuteAsync(async () => + breaker.Awaiting(x => x.ExecuteAsync(async () => { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); await TaskHelper.EmptyTask; + firstExecutionActive = false; return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - // Release first execution soon as second overlapping execution is done gathering data. - permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); - } - } + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); - [Fact] - public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + Task secondExecution = Task.Factory.StartNew(async () => + { + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, durationOfBreak); + try + { + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + await TaskHelper.EmptyTask; - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + return ResultPrimitive.Good; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + permitFirstExecutionEnd.Set(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } + } - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + [Fact] + public async Task Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Awaiting(x => x.ExecuteAsync(async () => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, durationOfBreak); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - await TaskHelper.EmptyTask; - firstExecutionActive = false; + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - return ResultPrimitive.Good; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - Task secondExecution = Task.Factory.StartNew(async () => + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - try + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - - await breaker.ExecuteAsync(async () => + breaker.Awaiting(x => x.ExecuteAsync(async () => { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); + + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); await TaskHelper.EmptyTask; + firstExecutionActive = false; return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); + + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + + Task secondExecution = Task.Factory.StartNew(async () => { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + + try + { + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + + await breaker.ExecuteAsync(async () => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Release first execution soon as second overlapping execution is done gathering data. + await TaskHelper.EmptyTask; + + return ResultPrimitive.Good; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } + + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + } } - } - #endregion + #endregion - #region Isolate and reset tests + #region Isolate and reset tests - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit - var delegateExecutedWhenBroken = false; - breaker.Awaiting(b => b.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); + // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit + var delegateExecutedWhenBroken = false; + breaker.Awaiting(b => b.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; - breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + var delegateExecutedWhenBroken = false; + breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().Throw(); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); - } + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + } - [Fact] - public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public async Task Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); - } + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + } - #endregion + #endregion - #region State-change delegate tests + #region State-change delegate tests - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action, TimeSpan> onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onResetCalled.Should().BeFalse(); - } + onResetCalled.Should().BeFalse(); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_automatically() - { - var onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_automatically() + { + var onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - var onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + var onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - breaker.Isolate(); + breaker.Isolate(); - onBreakCalled.Should().BeTrue(); - } + onBreakCalled.Should().BeTrue(); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - var onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + var onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - // call through circuit when already broken - should not retrigger onBreak - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + // call through circuit when already broken - should not retrigger onBreak + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - var onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + [Fact] + public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - Task longRunningExecution = Task.Factory.StartNew(async () => + var onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - breaker.CircuitState.Should().Be(CircuitState.Closed); - - (await breaker.ExecuteAsync(async () => + Task longRunningExecution = Task.Factory.StartNew(async () => { - await TaskHelper.EmptyTask; + breaker.CircuitState.Should().Be(CircuitState.Closed); + + (await breaker.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; - permitMainThreadToOpenCircuit.Set(); + permitMainThreadToOpenCircuit.Set(); // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); // Throw a further failure when rest of test has already broken the circuit. breaker.CircuitState.Should().Be(CircuitState.Open); - return ResultPrimitive.Fault; + return ResultPrimitive.Fault; - })).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. - }, TaskCreationOptions.LongRunning); + })).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. + }, TaskCreationOptions.LongRunning); - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); + + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } + } + + [Fact] + public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - // Break circuit in the normal manner: onBreak() should be called once. - breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); + + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // first call after duration is successful, so circuit should reset + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); } - } - [Fact] - public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action, TimeSpan> onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + onResetCalled.Should().BeFalse(); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - onBreakCalled.Should().Be(0); + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + onBreakCalled.Should().Be(0); - // first call after duration is successful, so circuit should reset - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - onResetCalled.Should().BeFalse(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + // first call after duration is successful, so circuit should reset + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().NotThrow(); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + [Fact] + public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); - - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - - // first call after duration is successful, so circuit should reset - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().NotThrow(); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); - - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); - - // 2 exception or fault raised, circuit is now open - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset, onHalfOpen); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().Be(0); - var durationOfBreak = TimeSpan.FromMinutes(1); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + // 2 exception or fault raised, circuit is now open + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().Throw(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) - .Should().NotThrow(); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #region Tests of supplied parameters to onBreak delegate + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public async Task Should_call_onbreak_with_the_last_handled_result() - { - ResultPrimitive? handledResult = null; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; - Action onReset = _ => { }; + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().Throw(); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Awaiting(x => x.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good))) + .Should().NotThrow(); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + #region Tests of supplied parameters to onBreak delegate - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_call_onbreak_with_the_last_handled_result() + { + ResultPrimitive? handledResult = null; - handledResult?.Should().Be(ResultPrimitive.Fault); - } + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; + Action onReset = _ => { }; - [Fact] - public async Task Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + handledResult?.Should().Be(ResultPrimitive.Fault); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - passedBreakTimespan.Should().Be(durationOfBreak); - } + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + var durationOfBreak = TimeSpan.FromMinutes(1); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.CircuitState.Should().Be(CircuitState.Open); - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + passedBreakTimespan.Should().Be(durationOfBreak); + } - #endregion + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - #region Tests that supplied context is passed to stage-change delegates + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - (await breaker.RaiseResultSequenceAsync(new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Tests that supplied context is passed to stage-change delegates - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + [Fact] + public async Task Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - [Fact] - public async Task Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - Action, TimeSpan, Context> onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var durationOfBreak = TimeSpan.FromMinutes(1); + (await breaker.RaiseResultSequenceAsync(new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public async Task Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - // first call after duration should invoke onReset, with context - await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key1 = "value1", key2 = "value2" }.AsDictionary()); + Action, TimeSpan, Context> onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak, onBreak, onReset); + + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // first call after duration should invoke onReset, with context + await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key1 = "value1", key2 = "value2" }.AsDictionary()); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - breaker.CircuitState.Should().Be(CircuitState.Open); + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - contextData.Should().BeEmpty(); - } + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + contextData.Should().BeEmpty(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - // 2 exception or fault raised, circuit is now open - (await breaker.RaiseResultSequenceAsync(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1), onBreak, onReset); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + var durationOfBreak = TimeSpan.FromMinutes(1); - // first call after duration is successful, so circuit should reset - await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - #endregion + // 2 exception or fault raised, circuit is now open + (await breaker.RaiseResultSequenceAsync(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - #endregion + SystemClock.UtcNow = () => time.Add(durationOfBreak); - #region LastHandledResult property + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - [Fact] - public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + // first call after duration is successful, so circuit should reset + await breaker.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + #endregion - [Fact] - public async Task Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + #endregion + + #region LastHandledResult property - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public async Task Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public async Task Should_set_LastHandledResult_to_last_handled_result_when_breaking() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_set_LastHandledResult_to_last_handled_result_when_breaking() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public async Task Should_set_LastHandledResult_to_default_on_circuit_reset() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public async Task Should_set_LastHandledResult_to_default_on_circuit_reset() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.Reset(); + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); - #endregion + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); - #region Cancellation support + breaker.Reset(); - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + #endregion - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation support - var scenario = new Scenario + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public async Task Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + (await breaker.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - breaker.Awaiting(x => x.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls."); - // Circuit is now broken. + attemptsInvoked.Should().Be(1); + } - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + [Fact] + public async Task Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1)); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + (await breaker.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - cancellationTokenSource.Cancel(); + breaker.Awaiting(x => x.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls."); + // Circuit is now broken. - var scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + cancellationTokenSource.Cancel(); - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; + + breaker.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(0); + } + + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish TaskCanceledExceptions thrown with different CancellationTokens. - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(2, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(2, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + var policyCancellationTokenSource = new CancellationTokenSource(); + var policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - implicitlyCapturedActionCancellationTokenSource.Cancel(); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + var attemptsInvoked = 0; - breaker.Awaiting(x => x.ExecuteAsync(async _ => + breaker.Awaiting(x => x.ExecuteAsync(async _ => { attemptsInvoked++; await TaskHelper.EmptyTask; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); return ResultPrimitive.Good; }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion - public void Dispose() - { - SystemClock.Reset(); + #endregion + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs index bc883068581..f9cde83bc9c 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs @@ -6,596 +6,597 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.CircuitBreaker; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CircuitBreakerTResultMixedResultExceptionSpecs : IDisposable +namespace Polly.Specs.CircuitBreaker { - #region Circuit-breaker threshold-to-break tests - - [Fact] - public void Should_open_circuit_with_exception_after_specified_number_of_specified_exception_have_been_returned_when_result_policy_handling_exceptions_only() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CircuitBreakerTResultMixedResultExceptionSpecs : IDisposable { - var breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + #region Circuit-breaker threshold-to-break tests + + [Fact] + public void Should_open_circuit_with_exception_after_specified_number_of_specified_exception_have_been_returned_when_result_policy_handling_exceptions_only() + { + var breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e. Result == ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_open_circuit_if_results_and_exceptions_returned_match_combination_of_the_result_and_exception_predicates() + { + var breaker = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_configured_results_or_exceptions() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public void Should_not_open_circuit_if_exception_thrown_is_not_one_of_the_configured_results_or_exceptions() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() + { + var breaker = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + // non-matched result predicate + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + // non-matched exception predicate + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__configuring_multiple_results_and_exceptions() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(4, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 4 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls.") + .WithInnerException(); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__when_configuring_multiple_results_and_exceptions() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(4, TimeSpan.FromMinutes(1)); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // 4 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.FaultAgain); + + breaker.CircuitState.Should().Be(CircuitState.Open); + } + + [Fact] + public void Should_not_open_circuit_if_result_raised_or_exception_thrown_is_not_one_of_the_handled_results_or_exceptions() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.CircuitState.Should().Be(CircuitState.Open); - } + #endregion - [Fact] - public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } + #region Circuit-breaker open->half-open->open/closed tests - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e. Result == ResultPrimitive.Fault); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.Fault); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_open_circuit_if_results_and_exceptions_returned_match_combination_of_the_result_and_exception_predicates() - { - var breaker = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_configured_results_or_exceptions() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_not_open_circuit_if_exception_thrown_is_not_one_of_the_configured_results_or_exceptions() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - [Fact] - public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() - { - var breaker = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - // non-matched result predicate - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - // non-matched exception predicate - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "value"))) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + SystemClock.UtcNow = () => time.Add(durationOfBreak); - [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__configuring_multiple_results_and_exceptions() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(4, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 4 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls.") - .WithInnerException(); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__when_configuring_multiple_results_and_exceptions() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(4, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 4 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Good)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.FaultAgain); - - breaker.CircuitState.Should().Be(CircuitState.Open); - } - - [Fact] - public void Should_not_open_circuit_if_result_raised_or_exception_thrown_is_not_one_of_the_handled_results_or_exceptions() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new ArgumentException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + // first call after duration returns a fault, so circuit should break again + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); - #endregion + } - #region Circuit-breaker open->half-open->open/closed tests + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration returns a fault, so circuit should break again + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); - // first call after duration returns a fault, so circuit should break again - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); + } - } + #endregion - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_an_exception() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + #region State-change delegate tests - var durationOfBreak = TimeSpan.FromMinutes(1); + #region Tests of supplied parameters to onBreak delegate - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_call_onbreak_with_the_last_handled_result() + { + ResultPrimitive? handledResult = null; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; + Action onReset = _ => { }; - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - // first call after duration returns a fault, so circuit should break again - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - } + handledResult?.Should().Be(ResultPrimitive.Fault); + } - #endregion + [Fact] + public void Should_call_onbreak_with_the_last_raised_exception() + { + Exception lastException = null; - #region State-change delegate tests + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { lastException = outcome.Exception; }; + Action onReset = _ => { }; - #region Tests of supplied parameters to onBreak delegate + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_the_last_handled_result() - { - ResultPrimitive? handledResult = null; + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; - Action onReset = _ => { }; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + lastException.Should().BeOfType(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #endregion - handledResult?.Should().Be(ResultPrimitive.Fault); - } + #region LastHandledResult and LastException property - [Fact] - public void Should_call_onbreak_with_the_last_raised_exception() - { - Exception lastException = null; + [Fact] + public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { lastException = outcome.Exception; }; - Action onReset = _ => { }; + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_set_LastException_on_exception_even_when_not_breaking() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - lastException.Should().BeOfType(); - } + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeOfType(); + } - #region LastHandledResult and LastException property + [Fact] + public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_set_LastException_to_last_exception_when_breaking() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public void Should_set_LastException_on_exception_even_when_not_breaking() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeOfType(); + } - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeOfType(); - } + [Fact] + public void Should_set_LastHandledResult_and_LastException_to_default_on_circuit_reset() + { + var breaker = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) + .Should().Throw(); - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } - - [Fact] - public void Should_set_LastException_to_last_exception_when_breaking() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.Reset(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeOfType(); - } - - [Fact] - public void Should_set_LastHandledResult_and_LastException_to_default_on_circuit_reset() - { - var breaker = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.Invoking(b => b.RaiseResultAndOrExceptionSequence(new DivideByZeroException())) - .Should().Throw(); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - - breaker.CircuitState.Should().Be(CircuitState.Open); - - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - - breaker.Reset(); - - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } - - #endregion - - - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs index d0b933a7ebc..378d1f352af 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs @@ -11,1581 +11,1582 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.CircuitBreaker; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class CircuitBreakerTResultSpecs : IDisposable +namespace Polly.Specs.CircuitBreaker { - #region Configuration tests - - [Fact] - public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + [Collection(Constants.SystemClockDependentTestCollection)] + public class CircuitBreakerTResultSpecs : IDisposable { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.MaxValue); + #region Configuration tests - var result = breaker.RaiseResultSequence(ResultPrimitive.Fault); - result.Should().Be(ResultPrimitive.Fault); - } + [Fact] + public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.MaxValue); - [Fact] - public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(0, TimeSpan.FromSeconds(10)); + var result = breaker.RaiseResultSequence(ResultPrimitive.Fault); + result.Should().Be(ResultPrimitive.Fault); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("handledEventsAllowedBeforeBreaking"); - } + [Fact] + public void Should_throw_if_faults_allowed_before_breaking_is_less_than_one() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(0, TimeSpan.FromSeconds(10)); - [Fact] - public void Should_throw_if_duration_of_break_is_less_than_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); + action.Should().Throw() + .And.ParamName.Should() + .Be("handledEventsAllowedBeforeBreaking"); + } - action.Should().Throw() - .And.ParamName.Should() - .Be("durationOfBreak"); - } + [Fact] + public void Should_throw_if_duration_of_break_is_less_than_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, -TimeSpan.FromSeconds(1)); - [Fact] - public void Should_be_able_to_handle_a_duration_of_break_of_zero() - { - Action action = () => Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.Zero); - action.Should().NotThrow(); - } + action.Should().Throw() + .And.ParamName.Should() + .Be("durationOfBreak"); + } - [Fact] - public void Should_initialise_to_closed_state() - { - var durationOfBreak = TimeSpan.FromMinutes(1); + [Fact] + public void Should_be_able_to_handle_a_duration_of_break_of_zero() + { + Action action = () => Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.Zero); + action.Should().NotThrow(); + } - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + [Fact] + public void Should_initialise_to_closed_state() + { + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - #endregion + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - #region Circuit-breaker threshold-to-break tests + #endregion - [Fact] - public void Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + #region Circuit-breaker threshold-to-break tests - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.RaiseResultSequence(ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.Fault); - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result == ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) - .ResultCode.Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) - .Should().Throw>() - .WithMessage("The circuit is now open and is not allowing calls.") - .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result == ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_not_open_circuit_if_result_returned_is_not_the_handled_result() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault)) + .ResultCode.Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(b => b.RaiseResultSequence(new ResultClass(ResultPrimitive.Good))) + .Should().Throw>() + .WithMessage("The circuit is now open and is not allowing calls.") + .Where(e => e.Result.ResultCode == ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + } - [Fact] - public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultYetAgain) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_not_open_circuit_if_result_returned_is_not_the_handled_result() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultYetAgain) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - [Fact] - public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() - { - var breaker = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) - .ResultCode.Should().Be(ResultPrimitive.FaultAgain); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #endregion + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - #region Circuit-breaker open->half-open->open/closed tests + breaker.RaiseResultSequence(ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_halfopen_circuit_after_the_specified_duration_has_passed() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() + { + var breaker = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - } + breaker.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain)) + .ResultCode.Should().Be(ResultPrimitive.FaultAgain); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + #endregion - var durationOfBreak = TimeSpan.FromMinutes(1); + #region Circuit-breaker open->half-open->open/closed tests - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + [Fact] + public void Should_halfopen_circuit_after_the_specified_duration_has_passed() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var durationOfBreak = TimeSpan.FromMinutes(1); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // first call after duration returns a fault, so circuit should break again - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + } - [Fact] - public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_again_after_the_specified_duration_has_passed_if_the_next_call_raises_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // circuit has been reset so should once again allow 2 faults to be raised before breaking - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + // first call after duration returns a fault, so circuit should break again + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + } - breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - } + [Fact] + public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_next_call_does_not_return_a_fault() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var durationOfBreak = TimeSpan.FromMinutes(1); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // 2 exception raised, circuit is now open + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // circuit has been reset so should once again allow 2 faults to be raised before breaking + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); + breaker.Invoking(b => b.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // OnActionPreExecute() should permit first execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should reject a second execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Allow another time window to pass (breaker should still be HalfOpen). - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // OnActionPreExecute() should now permit another trial execution. - breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - } + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - [Fact] - public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__test_execution_permit_directly() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); + + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + // OnActionPreExecute() should permit first execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + // OnActionPreExecute() should reject a second execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Allow another time window to pass (breaker should still be HalfOpen). + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). - // The second execution should be rejected due to the halfopen state. + // OnActionPreExecute() should now permit another trial execution. + breaker._breakerController.Invoking(c => c.OnActionPreExecute()).Should().NotThrow(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + } - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + [Fact] + public void Should_only_allow_single_execution_on_first_entering_halfopen_state__integration_test() { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => - { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - return ResultPrimitive.Good; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). + // The second execution should be rejected due to the halfopen state. - var secondExecution = Task.Factory.StartNew(() => + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - try + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => { - breaker.Execute(() => + breaker.Invoking(x => x.Execute(() => { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); + + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); + + // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + + var secondExecution = Task.Factory.StartNew(() => { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // Release first execution soon as second overlapping execution is done gathering data. + try + { + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + + return ResultPrimitive.Good; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } + + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); + + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should not have been permitted. - // - Second execution attempt should have been rejected with HalfOpen state as cause. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeFalse(); - secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should not have been permitted. + // - Second execution attempt should have been rejected with HalfOpen state as cause. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeFalse(); + secondDelegateRejectedInHalfOpenState.Should().BeTrue(); + } } - } - [Fact] - public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, durationOfBreak); + [Fact] + public void Should_allow_single_execution_per_break_duration_in_halfopen_state__integration_test() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, durationOfBreak); - // exception raised, circuit is now open. - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - // break duration passes, circuit now half open - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // exception raised, circuit is now open. + breaker.CircuitState.Should().Be(CircuitState.Open); - // Start one execution during the HalfOpen state. - // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. - // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. + // break duration passes, circuit now half open + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) - { - bool? firstDelegateExecutedInHalfOpenState = null; - bool? secondDelegateExecutedInHalfOpenState = null; - bool? secondDelegateRejectedInHalfOpenState = null; + // Start one execution during the HalfOpen state. + // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. + // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var firstExecutionActive = false; - // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (var permitFirstExecutionEnd = new ManualResetEvent(false)) { - breaker.Invoking(x => x.Execute(() => - { - firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + bool? firstDelegateExecutedInHalfOpenState = null; + bool? secondDelegateExecutedInHalfOpenState = null; + bool? secondDelegateRejectedInHalfOpenState = null; - // Signal the second execution can start, overlapping with this (the first) execution. - firstExecutionActive = true; - permitSecondExecutionAttempt.Set(); + var firstExecutionActive = false; + // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. + var firstExecution = Task.Factory.StartNew(() => + { + breaker.Invoking(x => x.Execute(() => + { + firstDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. - // Hold first execution open until second indicates it is no longer needed, or time out. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - firstExecutionActive = false; + // Signal the second execution can start, overlapping with this (the first) execution. + firstExecutionActive = true; + permitSecondExecutionAttempt.Set(); - return ResultPrimitive.Good; - })).Should().NotThrow(); - }, TaskCreationOptions.LongRunning); + // Hold first execution open until second indicates it is no longer needed, or time out. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); + firstExecutionActive = false; - // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. - permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); + return ResultPrimitive.Good; + })).Should().NotThrow(); + }, TaskCreationOptions.LongRunning); - var secondExecution = Task.Factory.StartNew(() => - { - // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). - firstExecutionActive.Should().BeTrue(); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. + permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - try + var secondExecution = Task.Factory.StartNew(() => { - SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); + // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). + firstExecutionActive.Should().BeTrue(); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.Execute(() => + try { - secondDelegateRejectedInHalfOpenState = false; - secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + SystemClock.UtcNow = () => time.Add(durationOfBreak).Add(durationOfBreak); - return ResultPrimitive.Good; - }); - } - catch (BrokenCircuitException) - { - secondDelegateExecutedInHalfOpenState = false; - secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. - } + breaker.Execute(() => + { + secondDelegateRejectedInHalfOpenState = false; + secondDelegateExecutedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested in Task and breaker here. + + return ResultPrimitive.Good; + }); + } + catch (BrokenCircuitException) + { + secondDelegateExecutedInHalfOpenState = false; + secondDelegateRejectedInHalfOpenState = breaker.CircuitState == CircuitState.HalfOpen; // For readability of test results, we assert on this at test end rather than nested here. + } + + // Release first execution soon as second overlapping execution is done gathering data. + permitFirstExecutionEnd.Set(); + }, TaskCreationOptions.LongRunning); - // Release first execution soon as second overlapping execution is done gathering data. + // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); permitFirstExecutionEnd.Set(); - }, TaskCreationOptions.LongRunning); - - // Graceful cleanup: allow executions time to end naturally; signal them to end if not; timeout any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - permitFirstExecutionEnd.WaitOne(testTimeoutToExposeDeadlocks); - permitFirstExecutionEnd.Set(); - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (firstExecution.IsFaulted) throw firstExecution.Exception; - if (secondExecution.IsFaulted) throw secondExecution.Exception; - firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); - secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); - - // Assert: - // - First execution should have been permitted and executed under a HalfOpen state - // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. - firstDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateExecutedInHalfOpenState.Should().BeTrue(); - secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (firstExecution.IsFaulted) throw firstExecution.Exception; + if (secondExecution.IsFaulted) throw secondExecution.Exception; + firstExecution.Status.Should().Be(TaskStatus.RanToCompletion); + secondExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // Assert: + // - First execution should have been permitted and executed under a HalfOpen state + // - Second overlapping execution in halfopen state should have been permitted, one breakDuration later. + firstDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateExecutedInHalfOpenState.Should().BeTrue(); + secondDelegateRejectedInHalfOpenState.Should().BeFalse(); + } } - } - #endregion + #endregion - #region Isolate and reset tests + #region Isolate and reset tests - [Fact] - public void Should_open_circuit_and_block_calls_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_open_circuit_and_block_calls_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good;})) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.LastException.Should().BeOfType(); - delegateExecutedWhenBroken.Should().BeFalse(); + // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good;})) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.LastException.Should().BeOfType(); + delegateExecutedWhenBroken.Should().BeFalse(); - } + } - [Fact] - public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_open() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; - breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) - .Should().Throw(); - delegateExecutedWhenBroken.Should().BeFalse(); - } + var delegateExecutedWhenBroken = false; + breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) + .Should().Throw(); + delegateExecutedWhenBroken.Should().BeFalse(); + } - [Fact] - public void Should_close_circuit_again_on_reset_after_manual_override() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_close_circuit_again_on_reset_after_manual_override() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.Closed); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); - } + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); + } - [Fact] - public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() - { - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + [Fact] + public void Should_be_able_to_reset_automatically_opened_circuit_without_specified_duration_passing() + { + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - var durationOfBreak = TimeSpan.FromMinutes(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); - // reset circuit, with no time having passed - breaker.Reset(); - SystemClock.UtcNow().Should().Be(time); - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); - } + // reset circuit, with no time having passed + breaker.Reset(); + SystemClock.UtcNow().Should().Be(time); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); + } - #endregion + #endregion - #region State-change delegate tests + #region State-change delegate tests - [Fact] - public void Should_not_call_onreset_on_initialise() - { - Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + [Fact] + public void Should_not_call_onreset_on_initialise() + { + Action, TimeSpan> onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onResetCalled.Should().BeFalse(); - } + onResetCalled.Should().BeFalse(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_automatically() - { - var onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_automatically() + { + var onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().BeFalse(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().BeFalse(); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().BeTrue(); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_manually() - { - var onBreakCalled = false; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_manually() + { + var onBreakCalled = false; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; + var onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - onBreakCalled.Should().BeFalse(); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + onBreakCalled.Should().BeFalse(); - breaker.Isolate(); + breaker.Isolate(); - onBreakCalled.Should().BeTrue(); - } + onBreakCalled.Should().BeTrue(); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() - { - var onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() + { + var onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onBreakCalled.Should().Be(0); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - // call through circuit when already broken - should not retrigger onBreak - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); + // call through circuit when already broken - should not retrigger onBreak + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - } + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } - [Fact] - public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() - { - var onBreakCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); - - // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + [Fact] + public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var longRunningExecution = Task.Factory.StartNew(() => + var onBreakCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { }; + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); + + // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. + var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - breaker.CircuitState.Should().Be(CircuitState.Closed); - - breaker.Execute(() => + var longRunningExecution = Task.Factory.StartNew(() => { - permitMainThreadToOpenCircuit.Set(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + + breaker.Execute(() => + { + permitMainThreadToOpenCircuit.Set(); // Hold this execution until rest of the test indicates it can proceed (or timeout, to expose deadlocks). permitLongRunningExecutionToReturnItsFailure.WaitOne(testTimeoutToExposeDeadlocks); // Throw a further failure when rest of test has already broken the circuit. breaker.CircuitState.Should().Be(CircuitState.Open); - return ResultPrimitive.Fault; + return ResultPrimitive.Fault; - }).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. - }, TaskCreationOptions.LongRunning); + }).Should().Be(ResultPrimitive.Fault); // However, since execution started when circuit was closed, BrokenCircuitException will not have been thrown on entry; the original fault should still be returned. + }, TaskCreationOptions.LongRunning); - permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + permitMainThreadToOpenCircuit.WaitOne(testTimeoutToExposeDeadlocks).Should().BeTrue(); + + // Break circuit in the normal manner: onBreak() should be called once. + breaker.CircuitState.Should().Be(CircuitState.Closed); + onBreakCalled.Should().Be(0); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + + // Permit the second (long-running) execution to hit the open circuit with its failure. + permitLongRunningExecutionToReturnItsFailure.Set(); + + // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. + longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); + if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + + // onBreak() should still only have been called once. + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); + } + } + + [Fact] + public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + + var time = 1.January(2000); + SystemClock.UtcNow = () => time; + + var durationOfBreak = TimeSpan.FromMinutes(1); + + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - // Break circuit in the normal manner: onBreak() should be called once. - breaker.CircuitState.Should().Be(CircuitState.Closed); onBreakCalled.Should().Be(0); + breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); + + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); + + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); onBreakCalled.Should().Be(1); - // Permit the second (long-running) execution to hit the open circuit with its failure. - permitLongRunningExecutionToReturnItsFailure.Set(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - // Graceful cleanup: allow executions time to end naturally; timeout if any deadlocks; expose any execution faults. This validates the test ran as expected (and background delegates are complete) before we assert on outcomes. - longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); - if (longRunningExecution.IsFaulted) throw longRunningExecution.Exception; - longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset + onResetCalled.Should().Be(0); - // onBreak() should still only have been called once. - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); } - } - [Fact] - public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + [Fact] + public void Should_not_call_onreset_on_successive_successful_calls() + { + Action, TimeSpan> onBreak = (_, _) => { }; + var onResetCalled = false; + var onReset = () => { onResetCalled = true; }; - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + onResetCalled.Should().BeFalse(); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); - onBreakCalled.Should().Be(0); + breaker.Execute(() => ResultPrimitive.Good); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().BeFalse(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); + var durationOfBreak = TimeSpan.FromMinutes(1); - SystemClock.UtcNow = () => time.Add(durationOfBreak); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset - onResetCalled.Should().Be(0); + onBreakCalled.Should().Be(0); - // first call after duration is successful, so circuit should reset - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); - [Fact] - public void Should_not_call_onreset_on_successive_successful_calls() - { - Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - onResetCalled.Should().BeFalse(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); + // first call after duration is successful, so circuit should reset + breaker.Execute(() => ResultPrimitive.Good); + onHalfOpenCalled.Should().Be(1); + breaker.CircuitState.Should().Be(CircuitState.Closed); + onResetCalled.Should().Be(1); + } - breaker.Execute(() => ResultPrimitive.Good); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().BeFalse(); - } + [Fact] + public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() + { + var onBreakCalled = 0; + var onResetCalled = 0; + var onHalfOpenCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; + var onHalfOpen = () => { onHalfOpenCalled++; }; - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - onHalfOpenCalled.Should().Be(0); // not yet transitioned to half-open, because we have not queried state - - // first call after duration is successful, so circuit should reset - breaker.Execute(() => ResultPrimitive.Good); - onHalfOpenCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Closed); - onResetCalled.Should().Be(1); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() - { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; - - var time = 1.January(2000); - SystemClock.UtcNow = () => time; - - var durationOfBreak = TimeSpan.FromMinutes(1); - - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - - onBreakCalled.Should().Be(0); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(0); - - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - onBreakCalled.Should().Be(1); - - // 2 exception raised, circuit is now open - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - onBreakCalled.Should().Be(1); - - SystemClock.UtcNow = () => time.Add(durationOfBreak); - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - onHalfOpenCalled.Should().Be(1); - } + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onreset_when_manually_resetting_circuit() - { - var onBreakCalled = 0; - var onResetCalled = 0; - Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + onBreakCalled.Should().Be(0); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(0); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + onBreakCalled.Should().Be(1); - onBreakCalled.Should().Be(0); - breaker.Isolate(); - onBreakCalled.Should().Be(1); + // 2 exception raised, circuit is now open + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + onBreakCalled.Should().Be(1); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) - .Should().Throw(); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + onHalfOpenCalled.Should().Be(1); + } - onResetCalled.Should().Be(0); - breaker.Reset(); - onResetCalled.Should().Be(1); + [Fact] + public void Should_call_onreset_when_manually_resetting_circuit() + { + var onBreakCalled = 0; + var onResetCalled = 0; + Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; + var onReset = () => { onResetCalled++; }; - breaker.CircuitState.Should().Be(CircuitState.Closed); - breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #region Tests of supplied parameters to onBreak delegate + var durationOfBreak = TimeSpan.FromMinutes(1); - [Fact] - public void Should_call_onbreak_with_the_last_handled_result() - { - ResultPrimitive? handledResult = null; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; - Action onReset = _ => { }; + onBreakCalled.Should().Be(0); + breaker.Isolate(); + onBreakCalled.Should().Be(1); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)) + .Should().Throw(); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + onResetCalled.Should().Be(0); + breaker.Reset(); + onResetCalled.Should().Be(1); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.Invoking(x => x.Execute(() => ResultPrimitive.Good)).Should().NotThrow(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + #region Tests of supplied parameters to onBreak delegate - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_call_onbreak_with_the_last_handled_result() + { + ResultPrimitive? handledResult = null; - handledResult?.Should().Be(ResultPrimitive.Fault); - } + Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; + Action onReset = _ => { }; - [Fact] - public void Should_call_onbreak_with_the_correct_timespan() - { - TimeSpan? passedBreakTimespan = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + handledResult?.Should().Be(ResultPrimitive.Fault); + } - breaker.CircuitState.Should().Be(CircuitState.Open); + [Fact] + public void Should_call_onbreak_with_the_correct_timespan() + { + TimeSpan? passedBreakTimespan = null; - passedBreakTimespan.Should().Be(durationOfBreak); - } + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - [Fact] - public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() - { - TimeSpan? passedBreakTimespan = null; - Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; - Action onReset = _ => { }; + var durationOfBreak = TimeSpan.FromMinutes(1); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - // manually break circuit - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.CircuitState.Should().Be(CircuitState.Open); - passedBreakTimespan.Should().Be(TimeSpan.MaxValue); - } + passedBreakTimespan.Should().Be(durationOfBreak); + } - #endregion + [Fact] + public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() + { + TimeSpan? passedBreakTimespan = null; + Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; + Action onReset = _ => { }; - #region Tests that supplied context is passed to stage-change delegates + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Should_call_onbreak_with_the_passed_context() - { - IDictionary contextData = null; + var durationOfBreak = TimeSpan.FromMinutes(1); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Closed); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + // manually break circuit + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + passedBreakTimespan.Should().Be(TimeSpan.MaxValue); + } - breaker.RaiseResultSequence(new {key1 = "value1", key2 = "value2"}.AsDictionary(), - ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Open); + #region Tests that supplied context is passed to stage-change delegates - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + [Fact] + public void Should_call_onbreak_with_the_passed_context() + { + IDictionary contextData = null; - [Fact] - public void Should_call_onreset_with_the_passed_context() - { - IDictionary contextData = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - Action, TimeSpan, Context> onBreak = (_, _, _) => { }; - Action onReset = context => { contextData = context; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.RaiseResultSequence(new {key1 = "value1", key2 = "value2"}.AsDictionary(), + ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak, onBreak, onReset); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + [Fact] + public void Should_call_onreset_with_the_passed_context() + { + IDictionary contextData = null; - // first call after duration should invoke onReset, with context - breaker.Execute(_ => ResultPrimitive.Good, new { key1 = "value1", key2 = "value2" }.AsDictionary()); + Action, TimeSpan, Context> onBreak = (_, _, _) => { }; + Action onReset = context => { contextData = context; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + var durationOfBreak = TimeSpan.FromMinutes(1); - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; - Action onReset = _ => { }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak, onBreak, onReset); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + SystemClock.UtcNow = () => time.Add(durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + // first call after duration should invoke onReset, with context + breaker.Execute(_ => ResultPrimitive.Good, new { key1 = "value1", key2 = "value2" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Open); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - contextData.Should().BeEmpty(); - } + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; + Action onReset = _ => { }; - Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var time = 1.January(2000); - SystemClock.UtcNow = () => time; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - var durationOfBreak = TimeSpan.FromMinutes(1); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + contextData.Should().BeEmpty(); + } - // 2 exception raised, circuit is now open - breaker.RaiseResultSequence(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - contextValue.Should().Be("original_value"); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - SystemClock.UtcNow = () => time.Add(durationOfBreak); + Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; + Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - // duration has passed, circuit now half open - breaker.CircuitState.Should().Be(CircuitState.HalfOpen); - // but not yet reset + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); - // first call after duration is successful, so circuit should reset - breaker.Execute(_ => ResultPrimitive.Good, new { key = "new_value" }.AsDictionary()); - breaker.CircuitState.Should().Be(CircuitState.Closed); - contextValue.Should().Be("new_value"); - } + var time = 1.January(2000); + SystemClock.UtcNow = () => time; - #endregion + var durationOfBreak = TimeSpan.FromMinutes(1); - #endregion + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - #region LastHandledResult property + // 2 exception raised, circuit is now open + breaker.RaiseResultSequence(new { key = "original_value" }.AsDictionary(), ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + contextValue.Should().Be("original_value"); - [Fact] - public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + SystemClock.UtcNow = () => time.Add(durationOfBreak); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + // duration has passed, circuit now half open + breaker.CircuitState.Should().Be(CircuitState.HalfOpen); + // but not yet reset - [Fact] - public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + // first call after duration is successful, so circuit should reset + breaker.Execute(_ => ResultPrimitive.Good, new { key = "new_value" }.AsDictionary()); + breaker.CircuitState.Should().Be(CircuitState.Closed); + contextValue.Should().Be("new_value"); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + #endregion - breaker.CircuitState.Should().Be(CircuitState.Closed); + #endregion - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + #region LastHandledResult property - [Fact] - public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); - } + breaker.CircuitState.Should().Be(CircuitState.Closed); - [Fact] - public void Should_set_LastHandledResult_to_default_on_circuit_reset() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); - breaker.LastException.Should().BeNull(); + breaker.CircuitState.Should().Be(CircuitState.Open); - breaker.Reset(); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); + } - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - breaker.LastException.Should().BeNull(); - } + [Fact] + public void Should_set_LastHandledResult_to_default_on_circuit_reset() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - #endregion + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - #region Cancellation support + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + breaker.CircuitState.Should().Be(CircuitState.Open); - var cancellationTokenSource = new CancellationTokenSource(); + breaker.LastHandledResult.Should().Be(ResultPrimitive.Fault); + breaker.LastException.Should().BeNull(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.Reset(); - var scenario = new Scenario + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastException.Should().BeNull(); + } + + #endregion + + #region Cancellation support + + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() - { - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + var cancellationTokenSource = new CancellationTokenSource(); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_when_both_open_circuit_and_cancellation() - { - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.FromMinutes(1)); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - breaker.RaiseResultSequence(ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); + breaker.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - breaker.Invoking(x => x.RaiseResultSequence(ResultPrimitive.Fault)) - .Should().Throw() - .WithMessage("The circuit is now open and is not allowing calls."); - // Circuit is now broken. + attemptsInvoked.Should().Be(1); + } - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + [Fact] + public void Should_report_cancellation_when_both_open_circuit_and_cancellation() + { + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.FromMinutes(1)); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + breaker.RaiseResultSequence(ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); - cancellationTokenSource.Cancel(); + breaker.Invoking(x => x.RaiseResultSequence(ResultPrimitive.Fault)) + .Should().Throw() + .WithMessage("The circuit is now open and is not allowing calls."); + // Circuit is now broken. - var scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. - ActionObservesCancellation = false - }; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + cancellationTokenSource.Cancel(); - [Fact] - public void Should_honour_different_cancellationToken_captured_implicitly_by_action() - { - // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. + ActionObservesCancellation = false + }; + + breaker.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(0); + } + + [Fact] + public void Should_honour_different_cancellationToken_captured_implicitly_by_action() + { + // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. - var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, durationOfBreak); + var durationOfBreak = TimeSpan.FromMinutes(1); + var breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + var policyCancellationTokenSource = new CancellationTokenSource(); + var policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; - implicitlyCapturedActionCancellationTokenSource.Cancel(); + implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + var attemptsInvoked = 0; - breaker.Invoking(x => x.Execute(_ => + breaker.Invoking(x => x.Execute(_ => { attemptsInvoked++; implicitlyCapturedActionCancellationToken.ThrowIfCancellationRequested(); return ResultPrimitive.Good; }, policyCancellationToken)) - .Should().Throw() - .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); + .Should().Throw() + .And.CancellationToken.Should().Be(implicitlyCapturedActionCancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs index 7a3ad592bf1..4854f25ba0d 100644 --- a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicySpecs.cs @@ -3,55 +3,56 @@ using Polly.CircuitBreaker; using Xunit; -namespace Polly.Specs.CircuitBreaker; - -public class ICircuitBreakerPolicySpecs +namespace Polly.Specs.CircuitBreaker { - [Fact] - public void Should_be_able_to_use_CircuitState_via_interface() + public class ICircuitBreakerPolicySpecs { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_be_able_to_use_CircuitState_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.CircuitState.Should().Be(CircuitState.Closed); + breaker.CircuitState.Should().Be(CircuitState.Closed); - } + } - [Fact] - public void Should_be_able_to_use_Isolate_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_be_able_to_use_Isolate_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); - } + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); + } - [Fact] - public void Should_be_able_to_use_Reset_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_be_able_to_use_Reset_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.Isolate(); - breaker.CircuitState.Should().Be(CircuitState.Isolated); + breaker.Isolate(); + breaker.CircuitState.Should().Be(CircuitState.Isolated); - breaker.Reset(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + breaker.Reset(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } - [Fact] - public void Should_be_able_to_use_LastException_via_interface() - { - ICircuitBreakerPolicy breaker = Policy - .Handle() - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_be_able_to_use_LastException_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .Handle() + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.LastException.Should().BeNull(); + breaker.LastException.Should().BeNull(); - } + } -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs index b679b3b2418..f4ab982fccb 100644 --- a/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/ICircuitBreakerPolicyTResultSpecs.cs @@ -4,19 +4,20 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.CircuitBreaker; - -public class ICircuitBreakerTResultPolicySpecs +namespace Polly.Specs.CircuitBreaker { - [Fact] - public void Should_be_able_to_use_LastHandledResult_via_interface() + public class ICircuitBreakerTResultPolicySpecs { - ICircuitBreakerPolicy breaker = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(2, TimeSpan.FromMinutes(1)); + [Fact] + public void Should_be_able_to_use_LastHandledResult_via_interface() + { + ICircuitBreakerPolicy breaker = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); + breaker.LastHandledResult.Should().Be(default(ResultPrimitive)); - } + } -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/ContextSpecs.cs b/src/Polly.Specs/ContextSpecs.cs index 18e88e7f4ba..96ae781f25b 100644 --- a/src/Polly.Specs/ContextSpecs.cs +++ b/src/Polly.Specs/ContextSpecs.cs @@ -3,54 +3,55 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs; - -public class ContextSpecs +namespace Polly.Specs { - [Fact] - public void Should_assign_OperationKey_from_constructor() + public class ContextSpecs { - var context = new Context("SomeKey"); + [Fact] + public void Should_assign_OperationKey_from_constructor() + { + var context = new Context("SomeKey"); - context.OperationKey.Should().Be("SomeKey"); + context.OperationKey.Should().Be("SomeKey"); - context.Keys.Count.Should().Be(0); - } + context.Keys.Count.Should().Be(0); + } - [Fact] - public void Should_assign_OperationKey_and_context_data_from_constructor() - { - var context = new Context("SomeKey", new { key1 = "value1", key2 = "value2" }.AsDictionary()); + [Fact] + public void Should_assign_OperationKey_and_context_data_from_constructor() + { + var context = new Context("SomeKey", new { key1 = "value1", key2 = "value2" }.AsDictionary()); - context.OperationKey.Should().Be("SomeKey"); - context["key1"].Should().Be("value1"); - context["key2"].Should().Be("value2"); - } + context.OperationKey.Should().Be("SomeKey"); + context["key1"].Should().Be("value1"); + context["key2"].Should().Be("value2"); + } - [Fact] - public void NoArgsCtor_should_assign_no_OperationKey() - { - var context = new Context(); + [Fact] + public void NoArgsCtor_should_assign_no_OperationKey() + { + var context = new Context(); - context.OperationKey.Should().BeNull(); - } + context.OperationKey.Should().BeNull(); + } - [Fact] - public void Should_assign_CorrelationId_when_accessed() - { - var context = new Context("SomeKey"); + [Fact] + public void Should_assign_CorrelationId_when_accessed() + { + var context = new Context("SomeKey"); - context.CorrelationId.Should().NotBeEmpty(); - } + context.CorrelationId.Should().NotBeEmpty(); + } - [Fact] - public void Should_return_consistent_CorrelationId() - { - var context = new Context("SomeKey"); + [Fact] + public void Should_return_consistent_CorrelationId() + { + var context = new Context("SomeKey"); - var retrieved1 = context.CorrelationId; - var retrieved2 = context.CorrelationId; + var retrieved1 = context.CorrelationId; + var retrieved2 = context.CorrelationId; - retrieved1.Should().Be(retrieved2); + retrieved1.Should().Be(retrieved2); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs index cc8447b3c23..905cc63bbdb 100644 --- a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs @@ -5,94 +5,95 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom; - -public class CustomAsyncSpecs +namespace Polly.Specs.Custom { - [Fact] - public void Should_be_able_to_construct_active_policy() + public class CustomAsyncSpecs { - var construct = () => + [Fact] + public void Should_be_able_to_construct_active_policy() { - var policy = AsyncPreExecutePolicy.CreateAsync(async () => + var construct = () => { - // Placeholder for more substantive async work. - Console.WriteLine("Do something"); - await Task.CompletedTask; - }); - }; - - construct.Should().NotThrow(); - } - - [Fact] - public void Active_policy_should_execute() - { - var preExecuted = false; - var policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); + var policy = AsyncPreExecutePolicy.CreateAsync(async () => + { + // Placeholder for more substantive async work. + Console.WriteLine("Do something"); + await Task.CompletedTask; + }); + }; + + construct.Should().NotThrow(); + } + + [Fact] + public void Active_policy_should_execute() + { + var preExecuted = false; + var policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); - var executed = false; + var executed = false; - policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; return Task.CompletedTask; })) - .Should().NotThrow(); + policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; return Task.CompletedTask; })) + .Should().NotThrow(); - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - [Fact] - public void Should_be_able_to_construct_reactive_policy() - { - var construct = () => + [Fact] + public void Should_be_able_to_construct_reactive_policy() { - var policy = Policy.Handle().WithBehaviourAsync(async ex => + var construct = () => { - // Placeholder for more substantive async work. - Console.WriteLine("Handling " + ex.Message); - await Task.CompletedTask; - }); - }; - - construct.Should().NotThrow(); - } - - [Fact] - public void Reactive_policy_should_handle_exception() - { - Exception handled = null; - var policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); + var policy = Policy.Handle().WithBehaviourAsync(async ex => + { + // Placeholder for more substantive async work. + Console.WriteLine("Handling " + ex.Message); + await Task.CompletedTask; + }); + }; + + construct.Should().NotThrow(); + } + + [Fact] + public void Reactive_policy_should_handle_exception() + { + Exception handled = null; + var policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); - Exception toThrow = new InvalidOperationException(); - var executed = false; + Exception toThrow = new InvalidOperationException(); + var executed = false; - policy.Awaiting(x => x.ExecuteAsync(() => + policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; throw toThrow; })) - .Should().Throw().Which.Should().Be(toThrow); - - executed.Should().BeTrue(); - handled.Should().Be(toThrow); - } + .Should().Throw().Which.Should().Be(toThrow); - [Fact] - public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() - { - Exception handled = null; - var policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); - - Exception toThrow = new NotImplementedException(); - var executed = false; + executed.Should().BeTrue(); + handled.Should().Be(toThrow); + } - policy.Awaiting(x => x.ExecuteAsync(() => - { - executed = true; - throw toThrow; - })) - .Should().Throw().Which.Should().Be(toThrow); - - executed.Should().BeTrue(); - handled.Should().Be(null); + [Fact] + public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() + { + Exception handled = null; + var policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); + + Exception toThrow = new NotImplementedException(); + var executed = false; + + policy.Awaiting(x => x.ExecuteAsync(() => + { + executed = true; + throw toThrow; + })) + .Should().Throw().Which.Should().Be(toThrow); + + executed.Should().BeTrue(); + handled.Should().Be(null); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Custom/CustomSpecs.cs b/src/Polly.Specs/Custom/CustomSpecs.cs index f69217fd199..86bfe8db9d8 100644 --- a/src/Polly.Specs/Custom/CustomSpecs.cs +++ b/src/Polly.Specs/Custom/CustomSpecs.cs @@ -4,82 +4,83 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom; - -public class CustomSpecs +namespace Polly.Specs.Custom { - [Fact] - public void Should_be_able_to_construct_active_policy() + public class CustomSpecs { - var construct = () => + [Fact] + public void Should_be_able_to_construct_active_policy() { - var policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); - }; + var construct = () => + { + var policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); + }; - construct.Should().NotThrow(); - } + construct.Should().NotThrow(); + } - [Fact] - public void Active_policy_should_execute() - { - var preExecuted = false; - var policy = PreExecutePolicy.Create(() => preExecuted = true); + [Fact] + public void Active_policy_should_execute() + { + var preExecuted = false; + var policy = PreExecutePolicy.Create(() => preExecuted = true); - var executed = false; + var executed = false; - policy.Invoking(x => x.Execute(() => { executed = true; })) - .Should().NotThrow(); + policy.Invoking(x => x.Execute(() => { executed = true; })) + .Should().NotThrow(); - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - [Fact] - public void Should_be_able_to_construct_reactive_policy() - { - var construct = () => + [Fact] + public void Should_be_able_to_construct_reactive_policy() { - var policy = Policy.Handle().WithBehaviour(ex => Console.WriteLine("Handling " + ex.Message)); - }; + var construct = () => + { + var policy = Policy.Handle().WithBehaviour(ex => Console.WriteLine("Handling " + ex.Message)); + }; - construct.Should().NotThrow(); - } + construct.Should().NotThrow(); + } - [Fact] - public void Reactive_policy_should_handle_exception() - { - Exception handled = null; - var policy = Policy.Handle().WithBehaviour(ex => handled = ex); + [Fact] + public void Reactive_policy_should_handle_exception() + { + Exception handled = null; + var policy = Policy.Handle().WithBehaviour(ex => handled = ex); - Exception toThrow = new InvalidOperationException(); - var executed = false; + Exception toThrow = new InvalidOperationException(); + var executed = false; - policy.Invoking(x => x.Execute(() => { - executed = true; - throw toThrow; - })) - .Should().Throw().Which.Should().Be(toThrow); + policy.Invoking(x => x.Execute(() => { + executed = true; + throw toThrow; + })) + .Should().Throw().Which.Should().Be(toThrow); - executed.Should().BeTrue(); - handled.Should().Be(toThrow); - } + executed.Should().BeTrue(); + handled.Should().Be(toThrow); + } - [Fact] - public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() - { - Exception handled = null; - var policy = Policy.Handle().WithBehaviour(ex => handled = ex); + [Fact] + public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() + { + Exception handled = null; + var policy = Policy.Handle().WithBehaviour(ex => handled = ex); - Exception toThrow = new NotImplementedException(); - var executed = false; + Exception toThrow = new NotImplementedException(); + var executed = false; - policy.Invoking(x => x.Execute(() => { - executed = true; - throw toThrow; - })) - .Should().Throw().Which.Should().Be(toThrow); + policy.Invoking(x => x.Execute(() => { + executed = true; + throw toThrow; + })) + .Should().Throw().Which.Should().Be(toThrow); - executed.Should().BeTrue(); - handled.Should().Be(null); + executed.Should().BeTrue(); + handled.Should().Be(null); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs index a046521cb69..4f5f88a603d 100644 --- a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs @@ -6,100 +6,101 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom; - -public class CustomTResultAsyncSpecs +namespace Polly.Specs.Custom { - [Fact] - public void Should_be_able_to_construct_active_policy() + public class CustomTResultAsyncSpecs { - var construct = () => + [Fact] + public void Should_be_able_to_construct_active_policy() { - var policy = AsyncPreExecutePolicy.CreateAsync(async () => + var construct = () => { - // Placeholder for more substantive async work. - Console.WriteLine("Do something"); - await Task.CompletedTask; - }); - }; - - construct.Should().NotThrow(); - } - - [Fact] - public void Active_policy_should_execute() - { - var preExecuted = false; - var policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); - - var executed = false; - policy.Awaiting(x => x.ExecuteAsync(async () => { executed = true; await Task.CompletedTask; return ResultPrimitive.Undefined; })) - .Should().NotThrow(); - - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } - - [Fact] - public void Should_be_able_to_construct_reactive_policy() - { - var construct = () => + var policy = AsyncPreExecutePolicy.CreateAsync(async () => + { + // Placeholder for more substantive async work. + Console.WriteLine("Do something"); + await Task.CompletedTask; + }); + }; + + construct.Should().NotThrow(); + } + + [Fact] + public void Active_policy_should_execute() { - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviourAsync(async outcome => - { - // Placeholder for more substantive async work. - Console.WriteLine("Handling " + outcome.Result); - await Task.CompletedTask; - }); - }; + var preExecuted = false; + var policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); - construct.Should().NotThrow(); - } + var executed = false; + policy.Awaiting(x => x.ExecuteAsync(async () => { executed = true; await Task.CompletedTask; return ResultPrimitive.Undefined; })) + .Should().NotThrow(); - [Fact] - public async Task Reactive_policy_should_handle_result() - { - var handled = ResultPrimitive.Undefined; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); - - var toReturn = ResultPrimitive.Fault; - var executed = false; + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - (await policy.ExecuteAsync(async () => + [Fact] + public void Should_be_able_to_construct_reactive_policy() + { + var construct = () => { - executed = true; - await Task.CompletedTask; - return toReturn; - })) - .Should().Be(toReturn); - - executed.Should().BeTrue(); - handled.Should().Be(toReturn); - } - - [Fact] - public async Task Reactive_policy_should_be_able_to_ignore_unhandled_result() - { - ResultPrimitive? handled = null; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); - - var toReturn = ResultPrimitive.FaultYetAgain; - var executed = false; + var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviourAsync(async outcome => + { + // Placeholder for more substantive async work. + Console.WriteLine("Handling " + outcome.Result); + await Task.CompletedTask; + }); + }; + + construct.Should().NotThrow(); + } + + [Fact] + public async Task Reactive_policy_should_handle_result() + { + var handled = ResultPrimitive.Undefined; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); + + var toReturn = ResultPrimitive.Fault; + var executed = false; + + (await policy.ExecuteAsync(async () => + { + executed = true; + await Task.CompletedTask; + return toReturn; + })) + .Should().Be(toReturn); + + executed.Should().BeTrue(); + handled.Should().Be(toReturn); + } + + [Fact] + public async Task Reactive_policy_should_be_able_to_ignore_unhandled_result() + { + ResultPrimitive? handled = null; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); + + var toReturn = ResultPrimitive.FaultYetAgain; + var executed = false; + + (await policy.ExecuteAsync(async () => + { + executed = true; + await Task.CompletedTask; + return toReturn; + })) + .Should().Be(toReturn); + + executed.Should().BeTrue(); + handled.Should().Be(null); + } - (await policy.ExecuteAsync(async () => - { - executed = true; - await Task.CompletedTask; - return toReturn; - })) - .Should().Be(toReturn); - - executed.Should().BeTrue(); - handled.Should().Be(null); } - -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Custom/CustomTResultSpecs.cs b/src/Polly.Specs/Custom/CustomTResultSpecs.cs index b6ce5282b50..43ae11be96e 100644 --- a/src/Polly.Specs/Custom/CustomTResultSpecs.cs +++ b/src/Polly.Specs/Custom/CustomTResultSpecs.cs @@ -5,88 +5,89 @@ using Polly.Specs.Helpers.Custom.PreExecute; using Xunit; -namespace Polly.Specs.Custom; - -public class CustomTResultSpecs +namespace Polly.Specs.Custom { - [Fact] - public void Should_be_able_to_construct_active_policy() + public class CustomTResultSpecs { - var construct = () => + [Fact] + public void Should_be_able_to_construct_active_policy() { - var policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); - }; + var construct = () => + { + var policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); + }; - construct.Should().NotThrow(); - } + construct.Should().NotThrow(); + } - [Fact] - public void Active_policy_should_execute() - { - var preExecuted = false; - var policy = PreExecutePolicy.Create(() => preExecuted = true); + [Fact] + public void Active_policy_should_execute() + { + var preExecuted = false; + var policy = PreExecutePolicy.Create(() => preExecuted = true); - var executed = false; + var executed = false; - policy.Invoking(x => x.Execute(() => { - executed = true; - return ResultPrimitive.Undefined; - })) - .Should().NotThrow(); + policy.Invoking(x => x.Execute(() => { + executed = true; + return ResultPrimitive.Undefined; + })) + .Should().NotThrow(); - executed.Should().BeTrue(); - preExecuted.Should().BeTrue(); - } + executed.Should().BeTrue(); + preExecuted.Should().BeTrue(); + } - [Fact] - public void Should_be_able_to_construct_reactive_policy() - { - var construct = () => + [Fact] + public void Should_be_able_to_construct_reactive_policy() { - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => Console.WriteLine("Handling " + outcome.Result)); - }; + var construct = () => + { + var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => Console.WriteLine("Handling " + outcome.Result)); + }; - construct.Should().NotThrow(); - } + construct.Should().NotThrow(); + } - [Fact] - public void Reactive_policy_should_handle_result() - { - var handled = ResultPrimitive.Undefined; - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); + [Fact] + public void Reactive_policy_should_handle_result() + { + var handled = ResultPrimitive.Undefined; + var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); - var toReturn = ResultPrimitive.Fault; - var executed = false; + var toReturn = ResultPrimitive.Fault; + var executed = false; - policy.Execute(() => - { - executed = true; - return toReturn; - }) - .Should().Be(toReturn); + policy.Execute(() => + { + executed = true; + return toReturn; + }) + .Should().Be(toReturn); - executed.Should().BeTrue(); - handled.Should().Be(toReturn); - } + executed.Should().BeTrue(); + handled.Should().Be(toReturn); + } - [Fact] - public void Reactive_policy_should_be_able_to_ignore_unhandled_result() - { - ResultPrimitive? handled = null; - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); + [Fact] + public void Reactive_policy_should_be_able_to_ignore_unhandled_result() + { + ResultPrimitive? handled = null; + var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); - var toReturn = ResultPrimitive.FaultYetAgain; - var executed = false; + var toReturn = ResultPrimitive.FaultYetAgain; + var executed = false; - policy.Execute(() => - { - executed = true; - return toReturn; - }) - .Should().Be(toReturn); + policy.Execute(() => + { + executed = true; + return toReturn; + }) + .Should().Be(toReturn); - executed.Should().BeTrue(); - handled.Should().Be(null); - } + executed.Should().BeTrue(); + handled.Should().Be(null); + } -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs index d791d73defc..d7b509a4be1 100644 --- a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs @@ -9,818 +9,819 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Fallback; - -public class FallbackAsyncSpecs +namespace Polly.Specs.Fallback { - #region Configuration guard condition tests - - [Fact] - public void Should_throw_when_fallback_func_is_null() + public class FallbackAsyncSpecs { - Func fallbackActionAsync = null; + #region Configuration guard condition tests - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_throw_when_fallback_func_is_null() + { + Func fallbackActionAsync = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - [Fact] - public void Should_throw_when_fallback_func_is_null_with_onFallback() - { - Func fallbackActionAsync = null; - Func onFallbackAsync = _ => TaskHelper.EmptyTask; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + [Fact] + public void Should_throw_when_fallback_func_is_null_with_onFallback() + { + Func fallbackActionAsync = null; + Func onFallbackAsync = _ => TaskHelper.EmptyTask; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public void Should_throw_when_fallback_func_is_null_with_onFallback_with_context() - { - Func fallbackActionAsync = null; - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + [Fact] + public void Should_throw_when_fallback_func_is_null_with_onFallback_with_context() + { + Func fallbackActionAsync = null; + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - Func fallbackActionAsync = _ => TaskHelper.EmptyTask; - Func onFallbackAsync = null; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + Func fallbackActionAsync = _ => TaskHelper.EmptyTask; + Func onFallbackAsync = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - Func onFallbackAsync = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - Action policy = () => Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + Action policy = () => Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - #endregion + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - #region Policy operation tests + #endregion - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_does_not_throw() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + #region Policy operation tests - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_does_not_throw() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); - [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle(_ => false) - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + var fallbackPolicy = Policy + .Handle(_ => false) + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); - [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle(_ => false) - .Or(_ => false) - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); + var fallbackPolicy = Policy + .Handle(_ => false) + .Or(_ => false) + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().Throw(); - [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle(_ => true) - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle(_ => true) + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle(_ => true) - .Or() - .FallbackAsync(fallbackActionAsync); + [Fact] + public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle(_ => true) + .Or() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); - [Fact] - public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => + fallbackActionExecuted.Should().BeTrue(); + } + + [Fact] + public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() { - fallbackActionExecuted = true; - throw new DivideByZeroException { HelpLink = "FromFallbackAction" }; - }; + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => + { + fallbackActionExecuted = true; + throw new DivideByZeroException { HelpLink = "FromFallbackAction" }; + }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync((e, _) => e.HelpLink = "FromExecuteDelegate")) - .Should().Throw().And.HelpLink.Should().Be("FromFallbackAction"); + fallbackPolicy.Awaiting(x => x.RaiseExceptionAsync((e, _) => e.HelpLink = "FromExecuteDelegate")) + .Should().Throw().And.HelpLink.Should().Be("FromFallbackAction"); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_throw_for_generic_method_execution_on_non_generic_policy() - { - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(_ => TaskHelper.EmptyTask); + [Fact] + public void Should_throw_for_generic_method_execution_on_non_generic_policy() + { + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(_ => TaskHelper.EmptyTask); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))).Should().Throw(); - } + fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))).Should().Throw(); + } - #endregion + #endregion - #region onPolicyEvent delegate tests + #region onPolicyEvent delegate tests - [Fact] - public async Task Should_call_onFallback_passing_exception_triggering_fallback() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public async Task Should_call_onFallback_passing_exception_triggering_fallback() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - Exception exceptionPassedToOnFallback = null; - Func onFallbackAsync = ex => { exceptionPassedToOnFallback = ex; return TaskHelper.EmptyTask; }; + Exception exceptionPassedToOnFallback = null; + Func onFallbackAsync = ex => { exceptionPassedToOnFallback = ex; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - Exception instanceToThrow = new ArgumentNullException("myParam"); - await fallbackPolicy.RaiseExceptionAsync(instanceToThrow); + Exception instanceToThrow = new ArgumentNullException("myParam"); + await fallbackPolicy.RaiseExceptionAsync(instanceToThrow); - fallbackActionExecuted.Should().BeTrue(); - exceptionPassedToOnFallback.Should().BeOfType(); - exceptionPassedToOnFallback.Should().Be(instanceToThrow); - } + fallbackActionExecuted.Should().BeTrue(); + exceptionPassedToOnFallback.Should().BeOfType(); + exceptionPassedToOnFallback.Should().Be(instanceToThrow); + } - [Fact] - public async Task Should_not_call_onFallback_when_executed_delegate_does_not_throw() - { - Func fallbackActionAsync = _ => TaskHelper.EmptyTask; + [Fact] + public async Task Should_not_call_onFallback_when_executed_delegate_does_not_throw() + { + Func fallbackActionAsync = _ => TaskHelper.EmptyTask; - var onFallbackExecuted = false; - Func onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; + var onFallbackExecuted = false; + Func onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); + await fallbackPolicy.ExecuteAsync(() => TaskHelper.EmptyTask); - onFallbackExecuted.Should().BeFalse(); - } + onFallbackExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Context passing tests + #region Context passing tests - [Fact] - public void Should_call_onFallback_with_the_passed_context() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + [Fact] + public void Should_call_onFallback_with_the_passed_context() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - IDictionary contextData = null; + IDictionary contextData = null; - Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), + fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + [Fact] + public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - IDictionary contextData = null; + IDictionary contextData = null; - Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + Func onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), + fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() - { - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + [Fact] + public void Should_call_onFallback_with_independent_context_for_independent_calls() + { + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - IDictionary contextData = new Dictionary(); + IDictionary contextData = new Dictionary(); - Func onFallbackAsync = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; return TaskHelper.EmptyTask; }; + Func onFallbackAsync = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key = "value1" }.AsDictionary())) - .Should().NotThrow(); + fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), new { key = "value1" }.AsDictionary())) + .Should().NotThrow(); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new DivideByZeroException(), new { key = "value2" }.AsDictionary())) - .Should().NotThrow(); + fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new DivideByZeroException(), new { key = "value2" }.AsDictionary())) + .Should().NotThrow(); - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(typeof(ArgumentNullException)); - contextData.Keys.Should().Contain(typeof(DivideByZeroException)); - contextData[typeof(ArgumentNullException)].Should().Be("value1"); - contextData[typeof(DivideByZeroException)].Should().Be("value2"); + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(typeof(ArgumentNullException)); + contextData.Keys.Should().Contain(typeof(DivideByZeroException)); + contextData[typeof(ArgumentNullException)].Should().Be("value1"); + contextData[typeof(DivideByZeroException)].Should().Be("value2"); - } + } - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var onFallbackExecuted = false; + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var onFallbackExecuted = false; - Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; - Func onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; + Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - await fallbackPolicy.RaiseExceptionAsync(); + await fallbackPolicy.RaiseExceptionAsync(); - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - [Fact] - public void Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; + Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + fallbackPolicy.Awaiting(p => p.ExecuteAsync(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; + Func fallbackActionAsync = (ctx, _) => { contextData = ctx; return TaskHelper.EmptyTask; }; - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var fallbackExecuted = false; + [Fact] + public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var fallbackExecuted = false; - Func fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; + Func fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; - Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + Func onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + var fallbackPolicy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - await fallbackPolicy.RaiseExceptionAsync(); + await fallbackPolicy.RaiseExceptionAsync(); - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - #endregion + #endregion - #region Exception passing tests + #region Exception passing tests - [Fact] - public void Should_call_fallbackAction_with_the_exception() - { - Exception fallbackException = null; + [Fact] + public void Should_call_fallbackAction_with_the_exception() + { + Exception fallbackException = null; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackFunc, onFallback); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackFunc, onFallback); - Exception instanceToThrow = new ArgumentNullException("myParam"); - fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrow(); + Exception instanceToThrow = new ArgumentNullException("myParam"); + fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrow(); - fallbackException.Should().Be(instanceToThrow); - } + fallbackException.Should().Be(instanceToThrow); + } - [Fact] - public void Should_call_fallbackAction_with_the_exception_when_execute_and_capture() - { - Exception fallbackException = null; + [Fact] + public void Should_call_fallbackAction_with_the_exception_when_execute_and_capture() + { + Exception fallbackException = null; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackFunc, onFallback); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackFunc, onFallback); - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(() => throw new ArgumentNullException())) - .Should().NotThrow(); + fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(() => throw new ArgumentNullException())) + .Should().NotThrow(); - fallbackException.Should().NotBeNull() - .And.BeOfType(typeof(ArgumentNullException)); - } + fallbackException.Should().NotBeNull() + .And.BeOfType(typeof(ArgumentNullException)); + } - [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() - { - Exception fallbackException = null; + [Fact] + public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() + { + Exception fallbackException = null; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .HandleInner() - .FallbackAsync(fallbackFunc, onFallback); + var fallbackPolicy = Policy + .HandleInner() + .FallbackAsync(fallbackFunc, onFallback); - Exception instanceToCapture = new ArgumentNullException("myParam"); - var instanceToThrow = new Exception(String.Empty, instanceToCapture); - fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrow(); + Exception instanceToCapture = new ArgumentNullException("myParam"); + var instanceToThrow = new Exception(String.Empty, instanceToCapture); + fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrow(); - fallbackException.Should().Be(instanceToCapture); - } + fallbackException.Should().Be(instanceToCapture); + } - [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() - { - Exception fallbackException = null; + [Fact] + public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() + { + Exception fallbackException = null; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .HandleInner() - .FallbackAsync(fallbackFunc, onFallback); + var fallbackPolicy = Policy + .HandleInner() + .FallbackAsync(fallbackFunc, onFallback); - Exception instanceToCapture = new ArgumentNullException("myParam"); - Exception instanceToThrow = new AggregateException(instanceToCapture); - fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) - .Should().NotThrow(); + Exception instanceToCapture = new ArgumentNullException("myParam"); + Exception instanceToThrow = new AggregateException(instanceToCapture); + fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) + .Should().NotThrow(); - fallbackException.Should().Be(instanceToCapture); - } + fallbackException.Should().Be(instanceToCapture); + } - [Fact] - public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() - { - Exception fallbackException = null; + [Fact] + public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() + { + Exception fallbackException = null; - Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; + Func fallbackFunc = (ex, _, _) => { fallbackException = ex; return TaskHelper.EmptyTask; }; - Func onFallback = (_, _) => TaskHelper.EmptyTask; + Func onFallback = (_, _) => TaskHelper.EmptyTask; - var fallbackPolicy = Policy - .Handle() - .FallbackAsync(fallbackFunc, onFallback); + var fallbackPolicy = Policy + .Handle() + .FallbackAsync(fallbackFunc, onFallback); - fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => throw new ArgumentNullException())) - .Should().Throw(); + fallbackPolicy.Awaiting(p => p.ExecuteAsync(() => throw new ArgumentNullException())) + .Should().Throw(); - fallbackException.Should().BeNull(); - } + fallbackException.Should().BeNull(); + } - #endregion + #endregion - #region Cancellation tests + #region Cancellation tests - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); + + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var scenario = new Scenario + fallbackActionExecuted.Should().BeFalse(); + } + + [Fact] + public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeTrue(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + fallbackActionExecuted.Should().BeFalse(); - cancellationTokenSource.Cancel(); + } - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + [Fact] + public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() + { + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackActionExecuted.Should().BeFalse(); + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .Or() + .FallbackAsync(fallbackActionAsync); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() - { - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .Or() - .FallbackAsync(fallbackActionAsync); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackActionExecuted.Should().BeTrue(); - } + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); - [Fact] - public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() - { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackActionExecuted.Should().BeFalse(); - } - [Fact] - public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() - { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); - attemptsInvoked.Should().Be(1); + var fallbackActionExecuted = false; + Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - fallbackActionExecuted.Should().BeFalse(); - } - [Fact] - public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() - { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var fallbackActionExecuted = false; - Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; + var policy = Policy + .Handle() + .FallbackAsync(fallbackActionAsync); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .FallbackAsync(fallbackActionAsync); + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + #endregion - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); - fallbackActionExecuted.Should().BeTrue(); } - - #endregion - - -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Fallback/FallbackSpecs.cs b/src/Polly.Specs/Fallback/FallbackSpecs.cs index e160d7de427..bdda7d69bde 100644 --- a/src/Polly.Specs/Fallback/FallbackSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackSpecs.cs @@ -8,1268 +8,1269 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensions.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Fallback; - -public class FallbackSpecs +namespace Polly.Specs.Fallback { - #region Configuration guard condition tests - - [Fact] - public void Should_throw_when_fallback_action_is_null() + public class FallbackSpecs { - Action fallbackAction = null; + #region Configuration guard condition tests - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction); + [Fact] + public void Should_throw_when_fallback_action_is_null() + { + Action fallbackAction = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null() - { - Action fallbackAction = null; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null() + { + Action fallbackAction = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback() - { - Action fallbackAction = null; - Action onFallback = _ => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback() + { + Action fallbackAction = null; + Action onFallback = _ => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() - { - Action fallbackAction = null; - Action onFallback = _ => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() + { + Action fallbackAction = null; + Action onFallback = _ => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() - { - Action fallbackAction = null; - Action onFallback = (_, _) => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() + { + Action fallbackAction = null; + Action onFallback = (_, _) => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() - { - Action fallbackAction = null; - Action onFallback = (_, _) => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() + { + Action fallbackAction = null; + Action onFallback = (_, _) => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - var fallbackAction = () => { }; - Action onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + var fallbackAction = () => { }; + Action onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() - { - Action fallbackAction = _ => { }; - Action onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() + { + Action fallbackAction = _ => { }; + Action onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Action fallbackAction = _ => { }; - Action onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Action fallbackAction = _ => { }; + Action onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() - { - Action fallbackAction = (_, _) => { }; - Action onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() + { + Action fallbackAction = (_, _) => { }; + Action onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .Handle() + .Fallback(fallbackAction, onFallback); - #endregion + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - #region Policy operation tests + #endregion - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_does_not_throw() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + #region Policy operation tests - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_does_not_throw() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Execute(() => { }); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Execute(() => { }); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - var fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - var fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + var fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle(_ => false) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + var fallbackPolicy = Policy + .Handle(_ => false) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - [Fact] - public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle(_ => false) - .Or(_ => false) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); + var fallbackPolicy = Policy + .Handle(_ => false) + .Or(_ => false) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().Throw(); - [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .Handle(_ => true) - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle(_ => true) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - [Fact] - public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackActionExecuted.Should().BeTrue(); + } - var fallbackPolicy = Policy - .Handle(_ => true) - .Or() - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); + var fallbackPolicy = Policy + .Handle(_ => true) + .Or() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.Invoking(x => x.RaiseException()).Should().NotThrow(); - [Fact] - public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => + fallbackActionExecuted.Should().BeTrue(); + } + + [Fact] + public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() { - fallbackActionExecuted = true; - throw new DivideByZeroException {HelpLink = "FromFallbackAction"}; - }; + var fallbackActionExecuted = false; + var fallbackAction = () => + { + fallbackActionExecuted = true; + throw new DivideByZeroException {HelpLink = "FromFallbackAction"}; + }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackPolicy.Invoking(x => x.RaiseException((e, _) => e.HelpLink = "FromExecuteDelegate")) - .Should().Throw().And.HelpLink.Should().Be("FromFallbackAction"); + fallbackPolicy.Invoking(x => x.RaiseException((e, _) => e.HelpLink = "FromExecuteDelegate")) + .Should().Throw().And.HelpLink.Should().Be("FromFallbackAction"); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_throw_for_generic_method_execution_on_non_generic_policy() - { - var fallbackPolicy = Policy - .Handle() - .Fallback(() => {}); + [Fact] + public void Should_throw_for_generic_method_execution_on_non_generic_policy() + { + var fallbackPolicy = Policy + .Handle() + .Fallback(() => {}); - fallbackPolicy.Invoking(p => p.Execute(() => 0)).Should().Throw(); - } + fallbackPolicy.Invoking(p => p.Execute(() => 0)).Should().Throw(); + } - #endregion + #endregion - #region HandleInner tests, inner of normal exceptions + #region HandleInner tests, inner of normal exceptions - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_where_policy_doesnt_handle_inner() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_where_policy_doesnt_handle_inner() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + var withInner = new Exception(String.Empty, new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_fallback_when_policy_handles_inner_and_executed_delegate_throws_as_non_inner() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_policy_handles_inner_and_executed_delegate_throws_as_non_inner() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - Exception nonInner = new DivideByZeroException(); + Exception nonInner = new DivideByZeroException(); - fallbackPolicy.Invoking(x => x.RaiseException(nonInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(nonInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_inner_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_inner_exception_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + var withInner = new Exception(String.Empty, new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_exceptions_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_exceptions_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .Handle() - .OrInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .Handle() + .OrInner() + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentException()); + var withInner = new Exception(String.Empty, new ArgumentException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_exception_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); + var withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_not_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_not_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentException()); + var withInner = new Exception(String.Empty, new ArgumentException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_fallback_when_inner_exception_thrown_matches_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_inner_exception_thrown_matches_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + var withInner = new Exception(String.Empty, new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_inner_nested_exception_thrown_matches_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_inner_nested_exception_thrown_matches_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); + var withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_inner_exception_thrown_matches_one_of_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_inner_exception_thrown_matches_one_of_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => true) - .OrInner(_ => true) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => true) + .OrInner(_ => true) + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentNullException()); + var withInner = new Exception(String.Empty, new ArgumentNullException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => false) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => false) + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + var withInner = new Exception(String.Empty, new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_any_of_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_any_of_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => false) - .OrInner(_ => false) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => false) + .OrInner(_ => false) + .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentNullException()); + var withInner = new Exception(String.Empty, new ArgumentNullException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region HandleInner tests, inner of aggregate exceptions + #region HandleInner tests, inner of aggregate exceptions - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_where_policy_doesnt_handle_inner() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_where_policy_doesnt_handle_inner() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new DivideByZeroException()); + Exception withInner = new AggregateException(new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new DivideByZeroException()); + Exception withInner = new AggregateException(new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_of_aggregate_exceptions_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_of_aggregate_exceptions_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .Handle() - .OrInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .Handle() + .OrInner() + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new ArgumentException()); + Exception withInner = new AggregateException(new ArgumentException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_aggregate_exception_with_inner_handled_by_policy_amongst_other_inners() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_aggregate_exception_with_inner_handled_by_policy_amongst_other_inners() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new ArgumentException(), new DivideByZeroException(), new ArgumentNullException()); + Exception withInner = new AggregateException(new ArgumentException(), new DivideByZeroException(), new ArgumentNullException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_of_aggregate_exception_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_of_aggregate_exception_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); + Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_not_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_not_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new ArgumentException()); + Exception withInner = new AggregateException(new ArgumentException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentException); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentException); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new DivideByZeroException()); + Exception withInner = new AggregateException(new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_inner_of_aggregate_nested_exception_thrown_matches_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_inner_of_aggregate_nested_exception_thrown_matches_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => true) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => true) + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); + Exception withInner = new AggregateException(new AggregateException(new DivideByZeroException())); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_one_of_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_one_of_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => true) - .OrInner(_ => true) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => true) + .OrInner(_ => true) + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new ArgumentNullException()); + Exception withInner = new AggregateException(new ArgumentNullException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => false) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => false) + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new DivideByZeroException()); + Exception withInner = new AggregateException(new DivideByZeroException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is DivideByZeroException); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_any_of_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_any_of_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy - .HandleInner(_ => false) - .OrInner(_ => false) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleInner(_ => false) + .OrInner(_ => false) + .Fallback(fallbackAction); - Exception withInner = new AggregateException(new ArgumentNullException()); + Exception withInner = new AggregateException(new ArgumentNullException()); - fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentNullException); + fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerExceptions.Should().ContainSingle(e => e is ArgumentNullException); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region onPolicyEvent delegate tests + #region onPolicyEvent delegate tests - [Fact] - public void Should_call_onFallback_passing_exception_triggering_fallback() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + [Fact] + public void Should_call_onFallback_passing_exception_triggering_fallback() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - Exception exceptionPassedToOnFallback = null; - Action onFallback = ex => { exceptionPassedToOnFallback = ex; }; + Exception exceptionPassedToOnFallback = null; + Action onFallback = ex => { exceptionPassedToOnFallback = ex; }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Exception instanceToThrow = new ArgumentNullException("myParam"); - fallbackPolicy.RaiseException(instanceToThrow); + Exception instanceToThrow = new ArgumentNullException("myParam"); + fallbackPolicy.RaiseException(instanceToThrow); - fallbackActionExecuted.Should().BeTrue(); - exceptionPassedToOnFallback.Should().BeOfType(); - exceptionPassedToOnFallback.Should().Be(instanceToThrow); - } + fallbackActionExecuted.Should().BeTrue(); + exceptionPassedToOnFallback.Should().BeOfType(); + exceptionPassedToOnFallback.Should().Be(instanceToThrow); + } - [Fact] - public void Should_not_call_onFallback_when_executed_delegate_does_not_throw() - { - var fallbackAction = () => { }; + [Fact] + public void Should_not_call_onFallback_when_executed_delegate_does_not_throw() + { + var fallbackAction = () => { }; - var onFallbackExecuted = false; - Action onFallback = _ => { onFallbackExecuted = true; }; + var onFallbackExecuted = false; + Action onFallback = _ => { onFallbackExecuted = true; }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - fallbackPolicy.Execute(() => { }); + fallbackPolicy.Execute(() => { }); - onFallbackExecuted.Should().BeFalse(); - } + onFallbackExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Context passing tests + #region Context passing tests - [Fact] - public void Should_call_onFallback_with_the_passed_context() - { - Action fallbackAction = _ => { }; + [Fact] + public void Should_call_onFallback_with_the_passed_context() + { + Action fallbackAction = _ => { }; - IDictionary contextData = null; + IDictionary contextData = null; - Action onFallback = (_, ctx) => { contextData = ctx; }; + Action onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), + fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), new {key1 = "value1", key2 = "value2"}.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Action fallbackAction = _ => { }; + [Fact] + public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Action fallbackAction = _ => { }; - IDictionary contextData = null; + IDictionary contextData = null; - Action onFallback = (_, ctx) => { contextData = ctx; }; + Action onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), + fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), new {key1 = "value1", key2 = "value2"}.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() - { - Action fallbackAction = _ => { }; + [Fact] + public void Should_call_onFallback_with_independent_context_for_independent_calls() + { + Action fallbackAction = _ => { }; - IDictionary contextData = new Dictionary(); + IDictionary contextData = new Dictionary(); - Action onFallback = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; }; + Action onFallback = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; }; - var fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction, onFallback); - fallbackPolicy.Invoking( + fallbackPolicy.Invoking( p => p.Execute(_ => throw new ArgumentNullException(), new {key = "value1"}.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - fallbackPolicy.Invoking( + fallbackPolicy.Invoking( p => p.Execute(_ => throw new DivideByZeroException(), new {key = "value2"}.AsDictionary())) - .Should().NotThrow(); - - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(typeof (ArgumentNullException)); - contextData.Keys.Should().Contain(typeof (DivideByZeroException)); - contextData[typeof (ArgumentNullException)].Should().Be("value1"); - contextData[typeof (DivideByZeroException)].Should().Be("value2"); - - } - - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var onFallbackExecuted = false; + .Should().NotThrow(); - Action fallbackAction = _ => { }; - Action onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(typeof (ArgumentNullException)); + contextData.Keys.Should().Contain(typeof (DivideByZeroException)); + contextData[typeof (ArgumentNullException)].Should().Be("value1"); + contextData[typeof (DivideByZeroException)].Should().Be("value2"); - var fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction, onFallback); + } - fallbackPolicy.RaiseException(); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var onFallbackExecuted = false; - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + Action fallbackAction = _ => { }; + Action onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; - [Fact] - public void Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + var fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction, onFallback); - Action fallbackAction = (ctx, _) => { contextData = ctx;}; + fallbackPolicy.RaiseException(); - Action onFallback = (_, _) => { }; + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + Action fallbackAction = (ctx, _) => { contextData = ctx;}; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + Action onFallback = (_, _) => { }; - [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action fallbackAction = (ctx, _) => { contextData = ctx; }; + fallbackPolicy.Invoking(p => p.Execute(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - Action onFallback = (_, _) => { }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + Action fallbackAction = (ctx, _) => { contextData = ctx; }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + Action onFallback = (_, _) => { }; - [Fact] - public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var fallbackExecuted = false; + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; }; - Action onFallback = (_, _) => {}; + fallbackPolicy.Invoking(p => p.ExecuteAndCapture(_ => throw new ArgumentNullException(), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - var fallbackPolicy = Policy - .Handle() - .Or() - .Fallback(fallbackAction, onFallback); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - fallbackPolicy.RaiseException(); + [Fact] + public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var fallbackExecuted = false; - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + Action fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; }; + Action onFallback = (_, _) => {}; - #endregion + var fallbackPolicy = Policy + .Handle() + .Or() + .Fallback(fallbackAction, onFallback); - #region Exception passing tests + fallbackPolicy.RaiseException(); - [Fact] - public void Should_call_fallbackAction_with_the_exception() - { - Exception fallbackException = null; + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + #endregion - Action onFallback = (_, _) => { }; + #region Exception passing tests - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_call_fallbackAction_with_the_exception() + { + Exception fallbackException = null; - Exception instanceToThrow = new ArgumentNullException("myParam"); - fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) - .Should().NotThrow(); + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - fallbackException.Should().Be(instanceToThrow); - } + Action onFallback = (_, _) => { }; - [Fact] - public void Should_call_fallbackAction_with_the_exception_when_execute_and_capture() - { - Exception fallbackException = null; + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + Exception instanceToThrow = new ArgumentNullException("myParam"); + fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) + .Should().NotThrow(); - Action onFallback = (_, _) => { }; + fallbackException.Should().Be(instanceToThrow); + } - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); - fallbackPolicy.Invoking(p => p.ExecuteAndCapture(() => throw new ArgumentNullException())) - .Should().NotThrow(); + [Fact] + public void Should_call_fallbackAction_with_the_exception_when_execute_and_capture() + { + Exception fallbackException = null; - fallbackException.Should().NotBeNull() - .And.BeOfType(typeof(ArgumentNullException)); - } + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() - { - Exception fallbackException = null; + Action onFallback = (_, _) => { }; - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); + fallbackPolicy.Invoking(p => p.ExecuteAndCapture(() => throw new ArgumentNullException())) + .Should().NotThrow(); - Action onFallback = (_, _) => { }; + fallbackException.Should().NotBeNull() + .And.BeOfType(typeof(ArgumentNullException)); + } - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrapped() + { + Exception fallbackException = null; - Exception instanceToCapture = new ArgumentNullException("myParam"); - var instanceToThrow = new Exception(String.Empty, instanceToCapture); - fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) - .Should().NotThrow(); + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - fallbackException.Should().Be(instanceToCapture); - } + Action onFallback = (_, _) => { }; - [Fact] - public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() - { - Exception fallbackException = null; + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction, onFallback); - Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; + Exception instanceToCapture = new ArgumentNullException("myParam"); + var instanceToThrow = new Exception(String.Empty, instanceToCapture); + fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) + .Should().NotThrow(); - Action onFallback = (_, _) => { }; + fallbackException.Should().Be(instanceToCapture); + } - var fallbackPolicy = Policy - .HandleInner() - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_exception_unwrapped() + { + Exception fallbackException = null; - Exception instanceToCapture = new ArgumentNullException("myParam"); - Exception instanceToThrow = new AggregateException(instanceToCapture); - fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) - .Should().NotThrow(); + Action fallbackAction = (ex, _, _) => { fallbackException = ex; }; - fallbackException.Should().Be(instanceToCapture); - } + Action onFallback = (_, _) => { }; - [Fact] - public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() - { - Exception fallbackException = null; + var fallbackPolicy = Policy + .HandleInner() + .Fallback(fallbackAction, onFallback); - Action fallbackAction = (ex, _, _) => { - fallbackException = ex; }; + Exception instanceToCapture = new ArgumentNullException("myParam"); + Exception instanceToThrow = new AggregateException(instanceToCapture); + fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) + .Should().NotThrow(); - Action onFallback = (_, _) => { }; + fallbackException.Should().Be(instanceToCapture); + } - var fallbackPolicy = Policy - .Handle() - .Fallback(fallbackAction, onFallback); - - fallbackPolicy.Invoking(p => p.Execute(() => throw new ArgumentNullException())) - .Should().Throw(); + [Fact] + public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhandled() + { + Exception fallbackException = null; - fallbackException.Should().BeNull(); - } + Action fallbackAction = (ex, _, _) => { + fallbackException = ex; }; - #endregion + Action onFallback = (_, _) => { }; - #region Cancellation tests + var fallbackPolicy = Policy + .Handle() + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + fallbackPolicy.Invoking(p => p.Execute(() => throw new ArgumentNullException())) + .Should().Throw(); - var policy = Policy - .Handle() - .Fallback(fallbackAction); + fallbackException.Should().BeNull(); + } - var cancellationTokenSource = new CancellationTokenSource(); + #endregion - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation tests - var scenario = new Scenario + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .Fallback(fallbackAction); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .Fallback(fallbackAction); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - cancellationTokenSource.Cancel(); + var policy = Policy + .Handle() + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - } + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + cancellationTokenSource.Cancel(); - var policy = Policy - .Handle() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .Or() - .Fallback(fallbackAction); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .Or() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .Fallback(fallbackAction); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .Fallback(fallbackAction); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); - attemptsInvoked.Should().Be(1); + var policy = Policy + .Handle() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .Handle() - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); - attemptsInvoked.Should().Be(1); + var cancellationTokenSource = new CancellationTokenSource(); - fallbackActionExecuted.Should().BeTrue(); - } + var policy = Policy + .Handle() + .Fallback(fallbackAction); + + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - #endregion + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); + attemptsInvoked.Should().Be(1); + fallbackActionExecuted.Should().BeTrue(); + } + #endregion -} \ No newline at end of file + + + } +} diff --git a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs index ac6ad23f998..bc8442bbb31 100644 --- a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs @@ -9,818 +9,819 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.Fallback; - -public class FallbackTResultAsyncSpecs +namespace Polly.Specs.Fallback { - #region Configuration guard condition tests - - [Fact] - public void Should_throw_when_fallback_action_is_null() + public class FallbackTResultAsyncSpecs { - Func> fallbackAction = null; + #region Configuration guard condition tests - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + [Fact] + public void Should_throw_when_fallback_action_is_null() + { + Func> fallbackAction = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback() - { - Func> fallbackAction = null; - Func, Task> onFallbackAsync = _ => TaskHelper.EmptyTask; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback() + { + Func> fallbackAction = null; + Func, Task> onFallbackAsync = _ => TaskHelper.EmptyTask; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() - { - Func> fallbackAction = null; - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() + { + Func> fallbackAction = null; + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); - Func, Task> onFallbackAsync = null; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); + Func, Task> onFallbackAsync = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() - { - Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); - Func, Task> onFallbackAsync = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() + { + Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); + Func, Task> onFallbackAsync = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - Func, Context, Task> onFallbackAsync = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - Func, Context, Task> onFallbackAsync = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallbackAsync"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - #endregion + policy.Should().Throw() + .And.ParamName.Should().Be("onFallbackAsync"); + } - #region Policy operation tests + #endregion - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + #region Policy operation tests - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.FaultAgain); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.FaultAgain); - [Fact] - public async Task Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() - { - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(ResultPrimitive.Substitute); + fallbackActionExecuted.Should().BeFalse(); + } - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); - } + [Fact] + public async Task Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() + { + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(ResultPrimitive.Substitute); - [Fact] - public async Task Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); - [Fact] - public async Task Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackActionExecuted.Should().BeTrue(); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) - .Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain)) + .Should().Be(ResultPrimitive.Substitute); - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackActionExecuted.Should().BeTrue(); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) - .Should().Be(ResultPrimitive.FaultYetAgain); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) + .Should().Be(ResultPrimitive.FaultYetAgain); - [Fact] - public async Task Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .HandleResult(_ => false) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); + var fallbackPolicy = Policy + .HandleResult(_ => false) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); - [Fact] - public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .HandleResult(r => r == ResultPrimitive.Fault) - .OrResult(r => r == ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) - .Should().Be(ResultPrimitive.FaultYetAgain); + var fallbackPolicy = Policy + .HandleResult(r => r == ResultPrimitive.Fault) + .OrResult(r => r == ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain)) + .Should().Be(ResultPrimitive.FaultYetAgain); - [Fact] - public async Task Should_execute_fallback_when_result_raised_matches_handling_predicates() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .HandleResult(_ => true) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_execute_fallback_when_result_raised_matches_handling_predicates() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) - .Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(_ => true) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) + .Should().Be(ResultPrimitive.Substitute); + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public async Task Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - var fallbackPolicy = Policy - .HandleResult(_ => true) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + [Fact] + public async Task Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) - .Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(_ => true) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Undefined)) + .Should().Be(ResultPrimitive.Substitute); - [Fact] - public async Task Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => + fallbackActionExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() { - fallbackActionExecuted = true; - return Task.FromResult(new ResultClass(ResultPrimitive.Fault, "FromFallbackAction")); - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => + { + fallbackActionExecuted = true; + return Task.FromResult(new ResultClass(ResultPrimitive.Fault, "FromFallbackAction")); + }; - var fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .FallbackAsync(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .FallbackAsync(fallbackAction); - (await fallbackPolicy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate"))) - .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); + (await fallbackPolicy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate"))) + .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - #endregion + #endregion - #region onPolicyEvent delegate tests + #region onPolicyEvent delegate tests - [Fact] - public async Task Should_call_onFallback_passing_result_triggering_fallback() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(new ResultClass(ResultPrimitive.Substitute)); }; + [Fact] + public async Task Should_call_onFallback_passing_result_triggering_fallback() + { + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(new ResultClass(ResultPrimitive.Substitute)); }; - ResultClass resultPassedToOnFallback = null; - Func, Task> onFallbackAsync = r => { resultPassedToOnFallback = r.Result; return TaskHelper.EmptyTask; }; + ResultClass resultPassedToOnFallback = null; + Func, Task> onFallbackAsync = r => { resultPassedToOnFallback = r.Result; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + var fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - var resultFromDelegate = new ResultClass(ResultPrimitive.Fault); - await fallbackPolicy.ExecuteAsync(() => Task.FromResult(resultFromDelegate)); + var resultFromDelegate = new ResultClass(ResultPrimitive.Fault); + await fallbackPolicy.ExecuteAsync(() => Task.FromResult(resultFromDelegate)); - fallbackActionExecuted.Should().BeTrue(); - resultPassedToOnFallback.Should().NotBeNull(); - resultPassedToOnFallback.Should().Be(resultFromDelegate); - } + fallbackActionExecuted.Should().BeTrue(); + resultPassedToOnFallback.Should().NotBeNull(); + resultPassedToOnFallback.Should().Be(resultFromDelegate); + } - [Fact] - public async Task Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() - { - Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); + [Fact] + public async Task Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() + { + Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); - var onFallbackExecuted = false; - Func, Task> onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; + var onFallbackExecuted = false; + Func, Task> onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - onFallbackExecuted.Should().BeFalse(); - } + onFallbackExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Context passing tests + #region Context passing tests - [Fact] - public void Should_call_onFallback_with_the_passed_context() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + [Fact] + public void Should_call_onFallback_with_the_passed_context() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - IDictionary contextData = null; + IDictionary contextData = null; - Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + .Result + .Should().Be(ResultPrimitive.Substitute); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public async Task Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + [Fact] + public async Task Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - IDictionary contextData = null; + IDictionary contextData = null; - Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; + Func, Context, Task> onFallbackAsync = (_, ctx) => { contextData = ctx; return TaskHelper.EmptyTask; }; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallbackAsync); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallbackAsync); - (await fallbackPolicy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), + (await fallbackPolicy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Result.Should().Be(ResultPrimitive.Substitute); + .Result.Should().Be(ResultPrimitive.Substitute); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() - { - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - - IDictionary contextData = new Dictionary(); - - Func, Context, Task> onFallbackAsync = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; return TaskHelper.EmptyTask; }; - - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction, onFallbackAsync); - - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), new { key = "value1" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + [Fact] + public void Should_call_onFallback_with_independent_context_for_independent_calls() + { + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.FaultAgain), new { key = "value2" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + IDictionary contextData = new Dictionary(); - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(ResultPrimitive.Fault); - contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); - contextData[ResultPrimitive.Fault].Should().Be("value1"); - contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); - } + Func, Context, Task> onFallbackAsync = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; return TaskHelper.EmptyTask; }; - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var onFallbackExecuted = false; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction, onFallbackAsync); - Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); - Func, Context, Task> onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), new { key = "value1" }.AsDictionary()) + .Result + .Should().Be(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction, onFallbackAsync); + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.FaultAgain), new { key = "value2" }.AsDictionary()) + .Result + .Should().Be(ResultPrimitive.Substitute); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(ResultPrimitive.Fault); + contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); + contextData[ResultPrimitive.Fault].Should().Be("value1"); + contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); + } - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var onFallbackExecuted = false; - [Fact] - public void Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); + Func, Context, Task> onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; - Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction, onFallbackAsync); - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result - .Should().Be(ResultPrimitive.Substitute); + [Fact] + public void Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + fallbackPolicy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Fault), + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Result + .Should().Be(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), - new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + [Fact] + public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + Func> fallbackActionAsync = (ctx, _) => { contextData = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var fallbackExecuted = false; + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - Func> fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return Task.FromResult(ResultPrimitive.Substitute); - }; - Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackActionAsync, onFallbackAsync); + fallbackPolicy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Fault), + new { key1 = "value1", key2 = "value2" }.AsDictionary())) + .Should().NotThrow(); - (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } + [Fact] + public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var fallbackExecuted = false; - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } - #endregion + Func> fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return Task.FromResult(ResultPrimitive.Substitute); + }; + Func, Context, Task> onFallbackAsync = (_, _) => TaskHelper.EmptyTask; - #region Fault passing tests + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackActionAsync, onFallbackAsync); - [Fact] - public async Task Should_call_fallbackAction_with_the_fault() - { - DelegateResult fallbackOutcome = null; + (await fallbackPolicy.RaiseResultSequenceAsync(ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); - Func, Context, CancellationToken, Task> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; - Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } + #endregion - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallback); + #region Fault passing tests - var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Fault)); - result.Should().Be(ResultPrimitive.Substitute); + [Fact] + public async Task Should_call_fallbackAction_with_the_fault() + { + DelegateResult fallbackOutcome = null; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + Func, Context, CancellationToken, Task> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_call_fallbackAction_with_the_fault_when_execute_and_capture() - { - DelegateResult fallbackOutcome = null; + Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; - Func, Context, CancellationToken, Task> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallback); - Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; + var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Fault)); + result.Should().Be(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallback); + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - var result = await fallbackPolicy.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); - result.Should().NotBeNull(); - result.Result.Should().Be(ResultPrimitive.Substitute); + [Fact] + public async Task Should_call_fallbackAction_with_the_fault_when_execute_and_capture() + { + DelegateResult fallbackOutcome = null; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + Func, Context, CancellationToken, Task> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; - [Fact] - public async Task Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() - { - DelegateResult fallbackOutcome = null; + Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; - Func, Context, CancellationToken, Task> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallback); - Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; + var result = await fallbackPolicy.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); + result.Should().NotBeNull(); + result.Result.Should().Be(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .FallbackAsync(fallbackAction, onFallback); + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); - result.Should().Be(ResultPrimitive.FaultAgain); + [Fact] + public async Task Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() + { + DelegateResult fallbackOutcome = null; - fallbackOutcome.Should().BeNull(); - } + Func, Context, CancellationToken, Task> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return Task.FromResult(ResultPrimitive.Substitute); }; - #endregion + Func, Context, Task> onFallback = (_, _) => TaskHelper.EmptyTask; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .FallbackAsync(fallbackAction, onFallback); - #region Cancellation tests + var result = await fallbackPolicy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); + result.Should().Be(ResultPrimitive.FaultAgain); - [Fact] - public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + fallbackOutcome.Should().BeNull(); + } - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + #endregion - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation tests - var scenario = new Scenario + [Fact] + public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - cancellationTokenSource.Cancel(); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - } + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + cancellationTokenSource.Cancel(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .FallbackAsync(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain)) - .Should().Be(ResultPrimitive.FaultYetAgain); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() - { - var fallbackActionExecuted = false; - Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .FallbackAsync(fallbackAction); + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain)) + .Should().Be(ResultPrimitive.FaultYetAgain); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .FallbackAsync(fallbackAction); + + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - #endregion + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); + fallbackActionExecuted.Should().BeTrue(); + } -} \ No newline at end of file + #endregion + + + } +} diff --git a/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs b/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs index 5d932c4bd96..5f44d9d94c4 100644 --- a/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs @@ -8,837 +8,838 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.Fallback; - -public class FallbackTResultSpecs +namespace Polly.Specs.Fallback { - #region Configuration guard condition tests - - [Fact] - public void Should_throw_when_fallback_action_is_null() + public class FallbackTResultSpecs { - Func fallbackAction = null; + #region Configuration guard condition tests - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_throw_when_fallback_action_is_null() + { + Func fallbackAction = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null() - { - Func fallbackAction = null; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null() + { + Func fallbackAction = null; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback() - { - Func fallbackAction = null; - Action> onFallback = _ => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback() + { + Func fallbackAction = null; + Action> onFallback = _ => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() - { - Func fallbackAction = null; - Action> onFallback = _ => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback() + { + Func fallbackAction = null; + Action> onFallback = _ => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() - { - Func fallbackAction = null; - Action, Context> onFallback = (_, _) => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_is_null_with_onFallback_with_context() + { + Func fallbackAction = null; + Action, Context> onFallback = (_, _) => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() - { - Func fallbackAction = null; - Action, Context> onFallback = (_, _) => { }; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onFallback_with_context() + { + Func fallbackAction = null; + Action, Context> onFallback = (_, _) => { }; - policy.Should().Throw() - .And.ParamName.Should().Be("fallbackAction"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null() - { - var fallbackAction = () => ResultPrimitive.Substitute; - Action> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("fallbackAction"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null() + { + var fallbackAction = () => ResultPrimitive.Substitute; + Action> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; - Action> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_action_with_cancellation() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; + Action> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; - Action, Context> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; + Action, Context> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() - { - Func fallbackAction = (_, _) => ResultPrimitive.Substitute; - Action, Context> onFallback = null; + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Should_throw_when_onFallback_delegate_is_null_with_context_with_action_with_cancellation() + { + Func fallbackAction = (_, _) => ResultPrimitive.Substitute; + Action, Context> onFallback = null; - policy.Should().Throw() - .And.ParamName.Should().Be("onFallback"); - } + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - #endregion + policy.Should().Throw() + .And.ParamName.Should().Be("onFallback"); + } - #region Policy operation tests + #endregion - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + #region Policy operation tests - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.Execute(() => ResultPrimitive.Good); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.Execute(() => ResultPrimitive.Good); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeFalse(); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.FaultAgain); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.FaultAgain); - [Fact] - public void Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() - { - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(ResultPrimitive.Substitute); + fallbackActionExecuted.Should().BeFalse(); + } - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); - } + [Fact] + public void Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() + { + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(ResultPrimitive.Substitute); - [Fact] - public void Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Substitute); - [Fact] - public void Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeTrue(); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + [Fact] + public void Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultAgain).Should().Be(ResultPrimitive.Substitute); - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackActionExecuted.Should().BeTrue(); + } + + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + [Fact] + public void Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy - .HandleResult(_ => false) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(_ => false) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Fault); + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault).Should().Be(ResultPrimitive.Fault); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + [Fact] + public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy - .HandleResult(r => r == ResultPrimitive.Fault) - .OrResult(r => r == ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(r => r == ResultPrimitive.Fault) + .OrResult(r => r == ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); + fallbackPolicy.RaiseResultSequence(ResultPrimitive.FaultYetAgain).Should().Be(ResultPrimitive.FaultYetAgain); - fallbackActionExecuted.Should().BeFalse(); - } + fallbackActionExecuted.Should().BeFalse(); + } - [Fact] - public void Should_execute_fallback_when_result_raised_matches_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + [Fact] + public void Should_execute_fallback_when_result_raised_matches_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy - .HandleResult(_ => true) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(_ => true) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + [Fact] + public void Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy - .HandleResult(_ => true) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(_ => true) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Undefined).Should().Be(ResultPrimitive.Substitute); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - [Fact] - public void Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() - { - var fallbackActionExecuted = false; - var fallbackAction = () => + [Fact] + public void Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() { - fallbackActionExecuted = true; - return new ResultClass(ResultPrimitive.Fault, "FromFallbackAction"); - }; + var fallbackActionExecuted = false; + var fallbackAction = () => + { + fallbackActionExecuted = true; + return new ResultClass(ResultPrimitive.Fault, "FromFallbackAction"); + }; - var fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Fallback(fallbackAction); + var fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Fallback(fallbackAction); - fallbackPolicy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate")) - .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); + fallbackPolicy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault, "FromExecuteDelegate")) + .Should().Match(r => r.ResultCode == ResultPrimitive.Fault && r.SomeString == "FromFallbackAction"); - fallbackActionExecuted.Should().BeTrue(); - } + fallbackActionExecuted.Should().BeTrue(); + } - #endregion + #endregion - #region onPolicyEvent delegate tests + #region onPolicyEvent delegate tests - [Fact] - public void Should_call_onFallback_passing_result_triggering_fallback() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return new ResultClass(ResultPrimitive.Substitute); }; + [Fact] + public void Should_call_onFallback_passing_result_triggering_fallback() + { + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return new ResultClass(ResultPrimitive.Substitute); }; - ResultClass resultPassedToOnFallback = null; - Action> onFallback = r => { resultPassedToOnFallback = r.Result; }; + ResultClass resultPassedToOnFallback = null; + Action> onFallback = r => { resultPassedToOnFallback = r.Result; }; - var fallbackPolicy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - var resultFromDelegate = new ResultClass(ResultPrimitive.Fault); - fallbackPolicy.Execute(() => resultFromDelegate); + var resultFromDelegate = new ResultClass(ResultPrimitive.Fault); + fallbackPolicy.Execute(() => resultFromDelegate); - fallbackActionExecuted.Should().BeTrue(); - resultPassedToOnFallback.Should().NotBeNull(); - resultPassedToOnFallback.Should().Be(resultFromDelegate); - } + fallbackActionExecuted.Should().BeTrue(); + resultPassedToOnFallback.Should().NotBeNull(); + resultPassedToOnFallback.Should().Be(resultFromDelegate); + } - [Fact] - public void Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() - { - var fallbackAction = () => ResultPrimitive.Substitute; + [Fact] + public void Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() + { + var fallbackAction = () => ResultPrimitive.Substitute; - var onFallbackExecuted = false; - Action> onFallback = _ => { onFallbackExecuted = true; }; + var onFallbackExecuted = false; + Action> onFallback = _ => { onFallbackExecuted = true; }; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - fallbackPolicy.Execute(() => ResultPrimitive.Good); + fallbackPolicy.Execute(() => ResultPrimitive.Good); - onFallbackExecuted.Should().BeFalse(); - } + onFallbackExecuted.Should().BeFalse(); + } - #endregion + #endregion - #region Context passing tests + #region Context passing tests - [Fact] - public void Should_call_onFallback_with_the_passed_context() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; + [Fact] + public void Should_call_onFallback_with_the_passed_context() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; - IDictionary contextData = null; + IDictionary contextData = null; - Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; + Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - fallbackPolicy.Execute(_ => ResultPrimitive.Fault, + fallbackPolicy.Execute(_ => ResultPrimitive.Fault, new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + .Should().Be(ResultPrimitive.Substitute); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; + [Fact] + public void Should_call_onFallback_with_the_passed_context_when_execute_and_capture() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; - IDictionary contextData = null; + IDictionary contextData = null; - Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; + Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, + fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result.Should().Be(ResultPrimitive.Substitute); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } - - [Fact] - public void Should_call_onFallback_with_independent_context_for_independent_calls() - { - Func fallbackAction = _ => ResultPrimitive.Substitute; + .Result.Should().Be(ResultPrimitive.Substitute); - IDictionary contextData = new Dictionary(); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action, Context> onFallback = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; }; + [Fact] + public void Should_call_onFallback_with_independent_context_for_independent_calls() + { + Func fallbackAction = _ => ResultPrimitive.Substitute; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction, onFallback); + IDictionary contextData = new Dictionary(); - fallbackPolicy.Execute(_ => ResultPrimitive.Fault, new { key = "value1" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; }; - fallbackPolicy.Execute(_ => ResultPrimitive.FaultAgain, new { key = "value2" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction, onFallback); - contextData.Count.Should().Be(2); - contextData.Keys.Should().Contain(ResultPrimitive.Fault); - contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); - contextData[ResultPrimitive.Fault].Should().Be("value1"); - contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); - } + fallbackPolicy.Execute(_ => ResultPrimitive.Fault, new { key = "value1" }.AsDictionary()) + .Should().Be(ResultPrimitive.Substitute); - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var onFallbackExecuted = false; + fallbackPolicy.Execute(_ => ResultPrimitive.FaultAgain, new { key = "value2" }.AsDictionary()) + .Should().Be(ResultPrimitive.Substitute); - Func fallbackAction = _ => ResultPrimitive.Substitute; - Action, Context> onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; + contextData.Count.Should().Be(2); + contextData.Keys.Should().Contain(ResultPrimitive.Fault); + contextData.Keys.Should().Contain(ResultPrimitive.FaultAgain); + contextData[ResultPrimitive.Fault].Should().Be("value1"); + contextData[ResultPrimitive.FaultAgain].Should().Be("value2"); + } - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction, onFallback); + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var onFallbackExecuted = false; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); + Func fallbackAction = _ => ResultPrimitive.Substitute; + Action, Context> onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; - onFallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_passed_context() - { - IDictionary contextData = null; + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); - Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; + onFallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } - Action, Context> onFallback = (_, _) => { }; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context() + { + IDictionary contextData = null; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; - fallbackPolicy.Execute(_ => ResultPrimitive.Fault, - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (_, _) => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + fallbackPolicy.Execute(_ => ResultPrimitive.Fault, + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Should().Be(ResultPrimitive.Substitute); - Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action, Context> onFallback = (_, _) => { }; + [Fact] + public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + Func fallbackAction = (ctx, _) => { contextData = ctx; return ResultPrimitive.Substitute; }; - fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, - new { key1 = "value1", key2 = "value2" }.AsDictionary()) - .Result.Should().Be(ResultPrimitive.Substitute); + Action, Context> onFallback = (_, _) => { }; - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - var fallbackExecuted = false; + fallbackPolicy.ExecuteAndCapture(_ => ResultPrimitive.Fault, + new { key1 = "value1", key2 = "value2" }.AsDictionary()) + .Result.Should().Be(ResultPrimitive.Substitute); - Func fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return ResultPrimitive.Substitute; }; + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - Action, Context> onFallback = (_, _) => { }; + [Fact] + public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; + var fallbackExecuted = false; - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction, onFallback); + Func fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return ResultPrimitive.Substitute; }; - fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); + Action, Context> onFallback = (_, _) => { }; - fallbackExecuted.Should().BeTrue(); - capturedContext.Should().BeEmpty(); - } - #endregion + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction, onFallback); - #region Fault passing tests + fallbackPolicy.RaiseResultSequence(ResultPrimitive.Fault); - [Fact] - public void Should_call_fallbackAction_with_the_fault() - { - DelegateResult fallbackOutcome = null; + fallbackExecuted.Should().BeTrue(); + capturedContext.Should().BeEmpty(); + } + #endregion - Func, Context, CancellationToken, ResultPrimitive> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; + #region Fault passing tests - Action, Context> onFallback = (_, _) => { }; - - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); - - fallbackPolicy.Execute(() => ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Substitute); + [Fact] + public void Should_call_fallbackAction_with_the_fault() + { + DelegateResult fallbackOutcome = null; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + Func, Context, CancellationToken, ResultPrimitive> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; - [Fact] - public void Should_call_fallbackAction_with_the_fault_when_execute_and_capture() - { - DelegateResult fallbackOutcome = null; + Action, Context> onFallback = (_, _) => { }; - Func, Context, CancellationToken, ResultPrimitive> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - Action, Context> onFallback = (_, _) => { }; + fallbackPolicy.Execute(() => ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - var result = fallbackPolicy.ExecuteAndCapture(() => ResultPrimitive.Fault); - result.Should().NotBeNull(); - result.Result.Should().Be(ResultPrimitive.Substitute); + [Fact] + public void Should_call_fallbackAction_with_the_fault_when_execute_and_capture() + { + DelegateResult fallbackOutcome = null; - fallbackOutcome.Should().NotBeNull(); - fallbackOutcome.Exception.Should().BeNull(); - fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); - } + Func, Context, CancellationToken, ResultPrimitive> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; - [Fact] - public void Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() - { - DelegateResult fallbackOutcome = null; + Action, Context> onFallback = (_, _) => { }; - Func, Context, CancellationToken, ResultPrimitive> fallbackAction = - (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - Action, Context> onFallback = (_, _) => { }; + var result = fallbackPolicy.ExecuteAndCapture(() => ResultPrimitive.Fault); + result.Should().NotBeNull(); + result.Result.Should().Be(ResultPrimitive.Substitute); - var fallbackPolicy = Policy - .HandleResult(ResultPrimitive.Fault) - .Fallback(fallbackAction, onFallback); + fallbackOutcome.Should().NotBeNull(); + fallbackOutcome.Exception.Should().BeNull(); + fallbackOutcome.Result.Should().Be(ResultPrimitive.Fault); + } - fallbackPolicy.Execute(() => ResultPrimitive.FaultAgain) - .Should().Be(ResultPrimitive.FaultAgain); + [Fact] + public void Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() + { + DelegateResult fallbackOutcome = null; - fallbackOutcome.Should().BeNull(); - } + Func, Context, CancellationToken, ResultPrimitive> fallbackAction = + (outcome, _, _) => { fallbackOutcome = outcome; return ResultPrimitive.Substitute; }; - #endregion + Action, Context> onFallback = (_, _) => { }; - #region Cancellation tests + var fallbackPolicy = Policy + .HandleResult(ResultPrimitive.Fault) + .Fallback(fallbackAction, onFallback); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + fallbackPolicy.Execute(() => ResultPrimitive.FaultAgain) + .Should().Be(ResultPrimitive.FaultAgain); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + fallbackOutcome.Should().BeNull(); + } - var cancellationTokenSource = new CancellationTokenSource(); + #endregion - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Cancellation tests - var scenario = new Scenario + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - cancellationTokenSource.Cancel(); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(0); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeFalse(); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - } + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + cancellationTokenSource.Cancel(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(0); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + fallbackActionExecuted.Should().BeFalse(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Fallback(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeTrue(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeTrue(); + } - var scenario = new Scenario + [Fact] + public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain) - .Should().Be(ResultPrimitive.FaultYetAgain); - attemptsInvoked.Should().Be(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); - fallbackActionExecuted.Should().BeFalse(); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() - { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Fallback(fallbackAction); + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.FaultYetAgain) + .Should().Be(ResultPrimitive.FaultYetAgain); + attemptsInvoked.Should().Be(1); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + fallbackActionExecuted.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var fallbackActionExecuted = false; + var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Substitute); - attemptsInvoked.Should().Be(1); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - fallbackActionExecuted.Should().BeTrue(); - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Fallback(fallbackAction); + + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - #endregion + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Substitute); + attemptsInvoked.Should().Be(1); + fallbackActionExecuted.Should().BeTrue(); + } -} \ No newline at end of file + #endregion + + + } +} diff --git a/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs b/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs index 8995afb4846..566053f9373 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/AnnotatedOutputHelper.cs @@ -4,59 +4,60 @@ using System.Threading; using Xunit.Abstractions; -namespace Polly.Specs.Helpers.Bulkhead; - -public class AnnotatedOutputHelper : ITestOutputHelper +namespace Polly.Specs.Helpers.Bulkhead { - private class Item + public class AnnotatedOutputHelper : ITestOutputHelper { - private static int monotonicSequence; - - public Item(string format, object[] args) + private class Item { - TimeStamp = DateTimeOffset.UtcNow; - Position = Interlocked.Increment(ref monotonicSequence); + private static int monotonicSequence; - Format = format; - Args = args; - } + public Item(string format, object[] args) + { + TimeStamp = DateTimeOffset.UtcNow; + Position = Interlocked.Increment(ref monotonicSequence); - public int Position { get; } - public DateTimeOffset TimeStamp { get; } - public string Format { get; } - public object[] Args { get; } - } + Format = format; + Args = args; + } - private readonly ConcurrentDictionary items = new ConcurrentDictionary(); + public int Position { get; } + public DateTimeOffset TimeStamp { get; } + public string Format { get; } + public object[] Args { get; } + } - private readonly object[] noArgs = Array.Empty(); + private readonly ConcurrentDictionary items = new ConcurrentDictionary(); - private readonly ITestOutputHelper innerOutputHelper; + private readonly object[] noArgs = Array.Empty(); - public AnnotatedOutputHelper(ITestOutputHelper innerOutputHelper) - { - this.innerOutputHelper = innerOutputHelper ?? throw new ArgumentNullException(nameof(innerOutputHelper)); - } + private readonly ITestOutputHelper innerOutputHelper; - public void Flush() - { - // Some IDEs limit the number of lines of output displayed in a test result. Display the lines in reverse order so that we always see the most recent. - var toOutput = items.Select(kvp => kvp.Value).OrderBy(i => i.Position).Reverse(); - foreach (var item in toOutput) + public AnnotatedOutputHelper(ITestOutputHelper innerOutputHelper) { - innerOutputHelper.WriteLine(item.TimeStamp.ToString("o") + ": " + item.Format, item.Args); + this.innerOutputHelper = innerOutputHelper ?? throw new ArgumentNullException(nameof(innerOutputHelper)); } - items.Clear(); - } + public void Flush() + { + // Some IDEs limit the number of lines of output displayed in a test result. Display the lines in reverse order so that we always see the most recent. + var toOutput = items.Select(kvp => kvp.Value).OrderBy(i => i.Position).Reverse(); + foreach (var item in toOutput) + { + innerOutputHelper.WriteLine(item.TimeStamp.ToString("o") + ": " + item.Format, item.Args); + } + + items.Clear(); + } - public void WriteLine(string message) - { - items.TryAdd(Guid.NewGuid(), new Item(message ?? string.Empty, noArgs)); - } + public void WriteLine(string message) + { + items.TryAdd(Guid.NewGuid(), new Item(message ?? string.Empty, noArgs)); + } - public void WriteLine(string format, params object[] args) - { - items.TryAdd(Guid.NewGuid(), new Item(format ?? string.Empty, args == null || args.Length == 0 ? noArgs : args)); + public void WriteLine(string format, params object[] args) + { + items.TryAdd(Guid.NewGuid(), new Item(format ?? string.Empty, args == null || args.Length == 0 ? noArgs : args)); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs b/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs index 080e7f6758c..3a46bceba25 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/AssertionFailure.cs @@ -1,19 +1,20 @@ using System; -namespace Polly.Specs.Helpers.Bulkhead; - -public class AssertionFailure +namespace Polly.Specs.Helpers.Bulkhead { - public AssertionFailure(int expected, int actual, string measure) + public class AssertionFailure { - if (string.IsNullOrWhiteSpace(measure)) throw new ArgumentNullException(nameof(measure)); + public AssertionFailure(int expected, int actual, string measure) + { + if (string.IsNullOrWhiteSpace(measure)) throw new ArgumentNullException(nameof(measure)); - Expected = expected; - Actual = actual; - Measure = measure; - } + Expected = expected; + Actual = actual; + Measure = measure; + } - public int Expected { get; } - public int Actual { get; } - public string Measure { get; } -} \ No newline at end of file + public int Expected { get; } + public int Actual { get; } + public string Measure { get; } + } +} diff --git a/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs b/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs index 2634981f010..ce84758bdb2 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/SilentOutputHelper.cs @@ -1,16 +1,17 @@ using Xunit.Abstractions; -namespace Polly.Specs.Helpers.Bulkhead; - -public class SilentOutputHelper : ITestOutputHelper +namespace Polly.Specs.Helpers.Bulkhead { - public void WriteLine(string message) + public class SilentOutputHelper : ITestOutputHelper { - // Do nothing: intentionally silent. - } + public void WriteLine(string message) + { + // Do nothing: intentionally silent. + } - public void WriteLine(string format, params object[] args) - { - // Do nothing: intentionally silent. + public void WriteLine(string format, params object[] args) + { + // Do nothing: intentionally silent. + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs b/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs index 6b30bb2caba..97372ff0ad1 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/TraceableAction.cs @@ -4,71 +4,71 @@ using Polly.Bulkhead; using Xunit.Abstractions; -namespace Polly.Specs.Helpers.Bulkhead; - -/// -/// A traceable action that can be executed on a , to support specs. -/// We can execute multiple instances of in parallel on a bulkhead, and manually control the cancellation and completion of each, to provide determinate tests on the bulkhead operation. The status of this as it executes is fully traceable through the property. -/// -public class TraceableAction : IDisposable +namespace Polly.Specs.Helpers.Bulkhead { - private readonly string _id; - private readonly ITestOutputHelper _testOutputHelper; + /// + /// A traceable action that can be executed on a , to support specs. + /// We can execute multiple instances of in parallel on a bulkhead, and manually control the cancellation and completion of each, to provide determinate tests on the bulkhead operation. The status of this as it executes is fully traceable through the property. + /// + public class TraceableAction : IDisposable + { + private readonly string _id; + private readonly ITestOutputHelper _testOutputHelper; - private readonly TaskCompletionSource _tcsProxyForRealWork = new TaskCompletionSource(); - private readonly CancellationTokenSource CancellationSource = new CancellationTokenSource(); + private readonly TaskCompletionSource _tcsProxyForRealWork = new TaskCompletionSource(); + private readonly CancellationTokenSource CancellationSource = new CancellationTokenSource(); - private TraceableActionStatus _status; - private readonly AutoResetEvent _statusChanged; + private TraceableActionStatus _status; + private readonly AutoResetEvent _statusChanged; - public TraceableActionStatus Status - { - get { return _status; } - set + public TraceableActionStatus Status { - _status = value; - _testOutputHelper.WriteLine(_id + "Updated status to {0}, signalling AutoResetEvent.", _status); - SignalStateChange(); + get { return _status; } + set + { + _status = value; + _testOutputHelper.WriteLine(_id + "Updated status to {0}, signalling AutoResetEvent.", _status); + SignalStateChange(); + } } - } - - public TraceableAction(int id, AutoResetEvent statusChanged, ITestOutputHelper testOutputHelper) - { - _id = $"{id:00}: "; - _statusChanged = statusChanged; - _testOutputHelper = testOutputHelper; - } - public void SignalStateChange() - { - _testOutputHelper.WriteLine("--signalled--"); - _statusChanged.Set(); - } + public TraceableAction(int id, AutoResetEvent statusChanged, ITestOutputHelper testOutputHelper) + { + _id = $"{id:00}: "; + _statusChanged = statusChanged; + _testOutputHelper = testOutputHelper; + } - public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) - { - return ExecuteThroughSyncBulkheadOuter( - () => bulkhead.Execute(_ => ExecuteThroughSyncBulkheadInner(), CancellationSource.Token) - ); - } + public void SignalStateChange() + { + _testOutputHelper.WriteLine("--signalled--"); + _statusChanged.Set(); + } - public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) - { - return ExecuteThroughSyncBulkheadOuter( - () => bulkhead.Execute(_ => { ExecuteThroughSyncBulkheadInner(); return default; }, CancellationSource.Token) - ); - } + public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) + { + return ExecuteThroughSyncBulkheadOuter( + () => bulkhead.Execute(_ => ExecuteThroughSyncBulkheadInner(), CancellationSource.Token) + ); + } - // Note re TaskCreationOptions.LongRunning: Testing the parallelization of the bulkhead policy efficiently requires the ability to start large numbers of parallel tasks in a short space of time. The ThreadPool's algorithm of only injecting extra threads (when necessary) at a rate of two-per-second however makes high-volume tests using the ThreadPool both slow and flaky. For PCL tests further, ThreadPool.SetMinThreads(...) is not available, to mitigate this. Using TaskCreationOptions.LongRunning allows us to force tasks to be started near-instantly on non-ThreadPool threads. - private Task ExecuteThroughSyncBulkheadOuter(Action executeThroughBulkheadInner) - { - if (Status != TraceableActionStatus.Unstarted) + public Task ExecuteOnBulkhead(BulkheadPolicy bulkhead) { - throw new InvalidOperationException(_id + "Action has previously been started."); + return ExecuteThroughSyncBulkheadOuter( + () => bulkhead.Execute(_ => { ExecuteThroughSyncBulkheadInner(); return default; }, CancellationSource.Token) + ); } - Status = TraceableActionStatus.StartRequested; - return Task.Factory.StartNew(() => + // Note re TaskCreationOptions.LongRunning: Testing the parallelization of the bulkhead policy efficiently requires the ability to start large numbers of parallel tasks in a short space of time. The ThreadPool's algorithm of only injecting extra threads (when necessary) at a rate of two-per-second however makes high-volume tests using the ThreadPool both slow and flaky. For PCL tests further, ThreadPool.SetMinThreads(...) is not available, to mitigate this. Using TaskCreationOptions.LongRunning allows us to force tasks to be started near-instantly on non-ThreadPool threads. + private Task ExecuteThroughSyncBulkheadOuter(Action executeThroughBulkheadInner) + { + if (Status != TraceableActionStatus.Unstarted) + { + throw new InvalidOperationException(_id + "Action has previously been started."); + } + Status = TraceableActionStatus.StartRequested; + + return Task.Factory.StartNew(() => { try { @@ -114,124 +114,125 @@ private Task ExecuteThroughSyncBulkheadOuter(Action executeThroughBulkheadInner) } }, TaskCreationOptions.LongRunning); - } - - private void ExecuteThroughSyncBulkheadInner() - { - Status = TraceableActionStatus.Executing; + } - _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously).Wait(); + private void ExecuteThroughSyncBulkheadInner() + { + Status = TraceableActionStatus.Executing; - _testOutputHelper.WriteLine(_id + "Exiting execution."); - } + _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously).Wait(); - public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) - { - return ExecuteThroughAsyncBulkheadOuter( - () => bulkhead.ExecuteAsync(async _ => await ExecuteThroughAsyncBulkheadInner(), CancellationSource.Token) - ); - } + _testOutputHelper.WriteLine(_id + "Exiting execution."); + } - public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) - { - return ExecuteThroughAsyncBulkheadOuter( - () => bulkhead.ExecuteAsync(async _ => { await ExecuteThroughAsyncBulkheadInner(); return default; }, CancellationSource.Token) - ); - } + public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) + { + return ExecuteThroughAsyncBulkheadOuter( + () => bulkhead.ExecuteAsync(async _ => await ExecuteThroughAsyncBulkheadInner(), CancellationSource.Token) + ); + } - public Task ExecuteThroughAsyncBulkheadOuter(Func executeThroughBulkheadInner) - { - if (Status != TraceableActionStatus.Unstarted) + public Task ExecuteOnBulkheadAsync(AsyncBulkheadPolicy bulkhead) { - throw new InvalidOperationException(_id + "Action has previously been started."); + return ExecuteThroughAsyncBulkheadOuter( + () => bulkhead.ExecuteAsync(async _ => { await ExecuteThroughAsyncBulkheadInner(); return default; }, CancellationSource.Token) + ); } - Status = TraceableActionStatus.StartRequested; - return Task.Factory.StartNew(async () => + public Task ExecuteThroughAsyncBulkheadOuter(Func executeThroughBulkheadInner) + { + if (Status != TraceableActionStatus.Unstarted) { - try - { - Status = TraceableActionStatus.QueueingForSemaphore; + throw new InvalidOperationException(_id + "Action has previously been started."); + } + Status = TraceableActionStatus.StartRequested; - await executeThroughBulkheadInner(); - } - catch (BulkheadRejectedException) + return Task.Factory.StartNew(async () => { - Status = TraceableActionStatus.Rejected; - } - catch (OperationCanceledException) - { - if (Status != TraceableActionStatus.Canceled) + try { - _testOutputHelper.WriteLine(_id + "Caught queue cancellation."); - Status = TraceableActionStatus.Canceled; - } // else: was execution cancellation rethrown: ignore - } - catch (Exception e) - { - _testOutputHelper.WriteLine(_id + "Caught unexpected exception during execution: " + e); - - Status = TraceableActionStatus.Faulted; - } - finally - { - // Exiting the execution successfully is also a change of state (on which assertions may be occurring) in that it releases a semaphore slot sucessfully. - // There can also be races between assertions and executions-responding-to-previous-state-changes, so a second signal presents another opportunity for assertions to be run. - SignalStateChange(); - } - }, - TaskCreationOptions.LongRunning).Unwrap(); - } + Status = TraceableActionStatus.QueueingForSemaphore; - private async Task ExecuteThroughAsyncBulkheadInner() - { - Status = TraceableActionStatus.Executing; - - await _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously); + await executeThroughBulkheadInner(); + } + catch (BulkheadRejectedException) + { + Status = TraceableActionStatus.Rejected; + } + catch (OperationCanceledException) + { + if (Status != TraceableActionStatus.Canceled) + { + _testOutputHelper.WriteLine(_id + "Caught queue cancellation."); + Status = TraceableActionStatus.Canceled; + } // else: was execution cancellation rethrown: ignore + } + catch (Exception e) + { + _testOutputHelper.WriteLine(_id + "Caught unexpected exception during execution: " + e); - _testOutputHelper.WriteLine(_id + "Exiting execution."); - } + Status = TraceableActionStatus.Faulted; + } + finally + { + // Exiting the execution successfully is also a change of state (on which assertions may be occurring) in that it releases a semaphore slot sucessfully. + // There can also be races between assertions and executions-responding-to-previous-state-changes, so a second signal presents another opportunity for assertions to be run. + SignalStateChange(); + } + }, + TaskCreationOptions.LongRunning).Unwrap(); + } - private Action> CaptureCompletion() => t => - { - if (t.IsCanceled) + private async Task ExecuteThroughAsyncBulkheadInner() { - _testOutputHelper.WriteLine(_id + "Cancelling execution."); + Status = TraceableActionStatus.Executing; - Status = TraceableActionStatus.Canceled; - throw new OperationCanceledException(CancellationSource.Token); // Exception rethrown for the purpose of testing exceptions thrown through the BulkheadEngine. - } - else if (t.IsFaulted) - { - _testOutputHelper.WriteLine(_id + "Execution faulted."); - if (t.Exception != null) { _testOutputHelper.WriteLine(_id + "Exception: " + t.Exception); } + await _tcsProxyForRealWork.Task.ContinueWith(CaptureCompletion(), TaskContinuationOptions.ExecuteSynchronously); - Status = TraceableActionStatus.Faulted; + _testOutputHelper.WriteLine(_id + "Exiting execution."); } - else + + private Action> CaptureCompletion() => t => { - _testOutputHelper.WriteLine(_id + "Completing execution."); + if (t.IsCanceled) + { + _testOutputHelper.WriteLine(_id + "Cancelling execution."); - Status = TraceableActionStatus.Completed; - } + Status = TraceableActionStatus.Canceled; + throw new OperationCanceledException(CancellationSource.Token); // Exception rethrown for the purpose of testing exceptions thrown through the BulkheadEngine. + } + else if (t.IsFaulted) + { + _testOutputHelper.WriteLine(_id + "Execution faulted."); + if (t.Exception != null) { _testOutputHelper.WriteLine(_id + "Exception: " + t.Exception); } + + Status = TraceableActionStatus.Faulted; + } + else + { + _testOutputHelper.WriteLine(_id + "Completing execution."); - }; + Status = TraceableActionStatus.Completed; + } - public void AllowCompletion() - { - _tcsProxyForRealWork.SetResult(null); - } + }; - public void Cancel() - { - if (CancellationSource.IsCancellationRequested) { throw new InvalidOperationException(_id + "Action has already been cancelled."); } - CancellationSource.Cancel(); + public void AllowCompletion() + { + _tcsProxyForRealWork.SetResult(null); + } - _tcsProxyForRealWork.SetCanceled(); - } + public void Cancel() + { + if (CancellationSource.IsCancellationRequested) { throw new InvalidOperationException(_id + "Action has already been cancelled."); } + CancellationSource.Cancel(); - public void Dispose() - { - CancellationSource.Dispose(); + _tcsProxyForRealWork.SetCanceled(); + } + + public void Dispose() + { + CancellationSource.Dispose(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs b/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs index 636af89073a..6b8153bbf68 100644 --- a/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs +++ b/src/Polly.Specs/Helpers/Bulkhead/TraceableActionStatus.cs @@ -1,16 +1,17 @@ -namespace Polly.Specs.Helpers.Bulkhead; - -/// -/// States of a that can be tracked during testing. -/// -public enum TraceableActionStatus +namespace Polly.Specs.Helpers.Bulkhead { - Unstarted, - StartRequested, - QueueingForSemaphore, - Executing, - Rejected, - Canceled, - Faulted, - Completed, + /// + /// States of a that can be tracked during testing. + /// + public enum TraceableActionStatus + { + Unstarted, + StartRequested, + QueueingForSemaphore, + Executing, + Rejected, + Canceled, + Faulted, + Completed, + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs b/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs index 7bb2ec9e8b4..6941c23bf73 100644 --- a/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs +++ b/src/Polly.Specs/Helpers/Caching/StubCacheKeyStrategy.cs @@ -1,22 +1,23 @@ using System; using Polly.Caching; -namespace Polly.Specs.Helpers.Caching; - -/// -/// A configurable stub ICacheKeyStrategy, to support tests.. -/// -internal class StubCacheKeyStrategy : ICacheKeyStrategy +namespace Polly.Specs.Helpers.Caching { - private readonly Func strategy; - - public StubCacheKeyStrategy(Func strategy) + /// + /// A configurable stub ICacheKeyStrategy, to support tests.. + /// + internal class StubCacheKeyStrategy : ICacheKeyStrategy { - this.strategy = strategy; - } + private readonly Func strategy; - public string GetCacheKey(Context context) - { - return strategy(context); + public StubCacheKeyStrategy(Func strategy) + { + this.strategy = strategy; + } + + public string GetCacheKey(Context context) + { + return strategy(context); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs b/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs index e9b577f3363..4f1af11cc8e 100644 --- a/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs +++ b/src/Polly.Specs/Helpers/Caching/StubCacheProvider.cs @@ -5,62 +5,63 @@ using Polly.Caching; using Polly.Utilities; -namespace Polly.Specs.Helpers.Caching; - -/// -/// An intentionally naive stub cache implementation. Its purpose is to be the simplest thing possible to support tests of the CachePolicy and CacheEngine, not a production-usable implementation. -/// -internal class StubCacheProvider : ISyncCacheProvider, IAsyncCacheProvider +namespace Polly.Specs.Helpers.Caching { - class CacheItem + /// + /// An intentionally naive stub cache implementation. Its purpose is to be the simplest thing possible to support tests of the CachePolicy and CacheEngine, not a production-usable implementation. + /// + internal class StubCacheProvider : ISyncCacheProvider, IAsyncCacheProvider { - public CacheItem(object value, Ttl ttl) + class CacheItem { - Expiry = DateTimeOffset.MaxValue - SystemClock.DateTimeOffsetUtcNow() > ttl.Timespan ? SystemClock.DateTimeOffsetUtcNow().Add(ttl.Timespan) : DateTimeOffset.MaxValue; - Value = value; - } + public CacheItem(object value, Ttl ttl) + { + Expiry = DateTimeOffset.MaxValue - SystemClock.DateTimeOffsetUtcNow() > ttl.Timespan ? SystemClock.DateTimeOffsetUtcNow().Add(ttl.Timespan) : DateTimeOffset.MaxValue; + Value = value; + } - public readonly DateTimeOffset Expiry; - public readonly object Value; - } + public readonly DateTimeOffset Expiry; + public readonly object Value; + } - private readonly Dictionary cachedValues = new Dictionary(); + private readonly Dictionary cachedValues = new Dictionary(); - public (bool, object) TryGet(string key) - { - if (cachedValues.ContainsKey(key)) + public (bool, object) TryGet(string key) { - if (SystemClock.DateTimeOffsetUtcNow() < cachedValues[key].Expiry) - { - return (true, cachedValues[key].Value); - } - else + if (cachedValues.ContainsKey(key)) { - cachedValues.Remove(key); + if (SystemClock.DateTimeOffsetUtcNow() < cachedValues[key].Expiry) + { + return (true, cachedValues[key].Value); + } + else + { + cachedValues.Remove(key); + } } + return (false, null); } - return (false, null); - } - public void Put(string key, object value, Ttl ttl) - { - cachedValues[key] = new CacheItem(value, ttl); - } + public void Put(string key, object value, Ttl ttl) + { + cachedValues[key] = new CacheItem(value, ttl); + } - #region Naive async-over-sync implementation + #region Naive async-over-sync implementation - // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return Task.FromResult(TryGet(key)); - } + // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } - public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - Put(key, value, ttl); - return TaskHelper.EmptyTask; - } + public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + Put(key, value, ttl); + return TaskHelper.EmptyTask; + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs b/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs index 2af6aade13f..24da3019c21 100644 --- a/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs +++ b/src/Polly.Specs/Helpers/Caching/StubErroringCacheProvider.cs @@ -4,47 +4,48 @@ using Polly.Caching; using Polly.Utilities; -namespace Polly.Specs.Helpers.Caching; - -internal class StubErroringCacheProvider : ISyncCacheProvider, IAsyncCacheProvider +namespace Polly.Specs.Helpers.Caching { - private Exception _getException; - private Exception _putException; + internal class StubErroringCacheProvider : ISyncCacheProvider, IAsyncCacheProvider + { + private Exception _getException; + private Exception _putException; - private StubCacheProvider innerProvider = new StubCacheProvider(); + private StubCacheProvider innerProvider = new StubCacheProvider(); - public StubErroringCacheProvider(Exception getException, Exception putException) - { - _getException = getException; - _putException = putException; - } + public StubErroringCacheProvider(Exception getException, Exception putException) + { + _getException = getException; + _putException = putException; + } - public (bool, object) TryGet(string key) - { - if (_getException != null) throw _getException; - return innerProvider.TryGet(key); - } + public (bool, object) TryGet(string key) + { + if (_getException != null) throw _getException; + return innerProvider.TryGet(key); + } - public void Put(string key, object value, Ttl ttl) - { - if (_putException != null) throw _putException; - innerProvider.Put(key, value, ttl); - } + public void Put(string key, object value, Ttl ttl) + { + if (_putException != null) throw _putException; + innerProvider.Put(key, value, ttl); + } - #region Naive async-over-sync implementation + #region Naive async-over-sync implementation - // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. - public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return Task.FromResult(TryGet(key)); - } + // Intentionally naive async-over-sync implementation. Its purpose is to be the simplest thing to support tests of the CachePolicyAsync and CacheEngineAsync, not to be a usable implementation of IAsyncCacheProvider. + public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return Task.FromResult(TryGet(key)); + } - public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - Put(key, value, ttl); - return TaskHelper.EmptyTask; - } + public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + Put(key, value, ttl); + return TaskHelper.EmptyTask; + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Helpers/Caching/StubSerialized.cs b/src/Polly.Specs/Helpers/Caching/StubSerialized.cs index 84a08d034c8..8266865f3f5 100644 --- a/src/Polly.Specs/Helpers/Caching/StubSerialized.cs +++ b/src/Polly.Specs/Helpers/Caching/StubSerialized.cs @@ -1,23 +1,24 @@ -namespace Polly.Specs.Helpers.Caching; - -/// -/// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! -/// -/// The type of the item being 'serialized'. -internal class StubSerialized +namespace Polly.Specs.Helpers.Caching { - public TOriginal Original; - - public StubSerialized(TOriginal original) + /// + /// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! + /// + /// The type of the item being 'serialized'. + internal class StubSerialized { - Original = original; + public TOriginal Original; + + public StubSerialized(TOriginal original) + { + Original = original; + } } -} -/// -/// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! -/// -internal class StubSerialized : StubSerialized -{ - public StubSerialized(object obj) : base(obj) { } + /// + /// An intentionally naive class to be the simplest thing possible to support tests around serializing cache providers. This serialization does nothing but wrap the object to be serialized! + /// + internal class StubSerialized : StubSerialized + { + public StubSerialized(object obj) : base(obj) { } + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Caching/StubSerializer.cs b/src/Polly.Specs/Helpers/Caching/StubSerializer.cs index 55d4fc18d3c..7c5414c8f17 100644 --- a/src/Polly.Specs/Helpers/Caching/StubSerializer.cs +++ b/src/Polly.Specs/Helpers/Caching/StubSerializer.cs @@ -1,24 +1,25 @@ using System; using Polly.Caching; -namespace Polly.Specs.Helpers.Caching; - -/// -/// A configurable stub serializer implementation to support tests around serializing cache providers. -/// -/// The type of the results being cached. -/// The type of the serialized values. -internal class StubSerializer : ICacheItemSerializer +namespace Polly.Specs.Helpers.Caching { - private readonly Func _serialize; - private readonly Func _deserialize; - - public StubSerializer(Func serialize, Func deserialize) + /// + /// A configurable stub serializer implementation to support tests around serializing cache providers. + /// + /// The type of the results being cached. + /// The type of the serialized values. + internal class StubSerializer : ICacheItemSerializer { - _serialize = serialize; - _deserialize = deserialize; - } - public TSerialized Serialize(TResult objectToSerialize) => _serialize(objectToSerialize); + private readonly Func _serialize; + private readonly Func _deserialize; - public TResult Deserialize(TSerialized objectToDeserialize) => _deserialize(objectToDeserialize); -} \ No newline at end of file + public StubSerializer(Func serialize, Func deserialize) + { + _serialize = serialize; + _deserialize = deserialize; + } + public TSerialized Serialize(TResult objectToSerialize) => _serialize(objectToSerialize); + + public TResult Deserialize(TSerialized objectToDeserialize) => _deserialize(objectToDeserialize); + } +} diff --git a/src/Polly.Specs/Helpers/Constants.cs b/src/Polly.Specs/Helpers/Constants.cs index cdc64c2ea26..c2b7f2816c5 100644 --- a/src/Polly.Specs/Helpers/Constants.cs +++ b/src/Polly.Specs/Helpers/Constants.cs @@ -1,17 +1,18 @@ -namespace Polly.Specs.Helpers; - -/// -/// Constants supporting tests. -/// -public class Constants +namespace Polly.Specs.Helpers { /// - /// Denotes a test collection dependent on manipulating the abstracted . These tests are not parallelized. + /// Constants supporting tests. /// - public const string SystemClockDependentTestCollection = "SystemClockDependentTestCollection"; + public class Constants + { + /// + /// Denotes a test collection dependent on manipulating the abstracted . These tests are not parallelized. + /// + public const string SystemClockDependentTestCollection = "SystemClockDependentTestCollection"; - /// - /// Denotes a test collection making heavy use of parallel threads. These tests are not run in parallel with each other, to reduce heavy use of threads in the build/CI environment. - /// - public const string ParallelThreadDependentTestCollection = "ParallelThreadDependentTestCollection"; + /// + /// Denotes a test collection making heavy use of parallel threads. These tests are not run in parallel with each other, to reduce heavy use of threads in the build/CI environment. + /// + public const string ParallelThreadDependentTestCollection = "ParallelThreadDependentTestCollection"; + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs b/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs index 0f1e48613c0..e0197610f0d 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs @@ -1,38 +1,39 @@ using System; using System.Collections.Generic; -namespace Polly.Specs.Helpers; - -public static class ContextualPolicyExtensions +namespace Polly.Specs.Helpers { - public static void RaiseException(this Policy policy, - int numberOfTimesToRaiseException, - IDictionary contextData, - Action configureException = null) where TException : Exception, new() + public static class ContextualPolicyExtensions { - var counter = 0; - - policy.Execute(_ => + public static void RaiseException(this Policy policy, + int numberOfTimesToRaiseException, + IDictionary contextData, + Action configureException = null) where TException : Exception, new() { - if (counter < numberOfTimesToRaiseException) + var counter = 0; + + policy.Execute(_ => { - counter++; + if (counter < numberOfTimesToRaiseException) + { + counter++; - var exception = new TException(); + var exception = new TException(); - configureException?.Invoke(exception, counter); + configureException?.Invoke(exception, counter); - throw exception; - } - }, contextData); - } + throw exception; + } + }, contextData); + } - public static void RaiseException( - this Policy policy, - IDictionary contextData, - Action configureException = null) where TException : Exception, new() - { - policy.RaiseException(1, contextData, configureException); - } + public static void RaiseException( + this Policy policy, + IDictionary contextData, + Action configureException = null) where TException : Exception, new() + { + policy.RaiseException(1, contextData, configureException); + } + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs index 020e4dfc7cf..5e5078e48e1 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs @@ -4,34 +4,35 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Specs.Helpers; - -public static class ContextualPolicyExtensionsAsync +namespace Polly.Specs.Helpers { - - public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() + public static class ContextualPolicyExtensionsAsync { - var counter = 0; - return policy.ExecuteAsync((_, _) => + public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() { - if (counter < numberOfTimesToRaiseException) + var counter = 0; + + return policy.ExecuteAsync((_, _) => { - counter++; + if (counter < numberOfTimesToRaiseException) + { + counter++; - var exception = new TException(); + var exception = new TException(); - configureException?.Invoke(exception, counter); + configureException?.Invoke(exception, counter); - throw exception; - } - return TaskHelper.EmptyTask; - }, contextData, cancellationToken); - } + throw exception; + } + return TaskHelper.EmptyTask; + }, contextData, cancellationToken); + } - public static Task RaiseExceptionAsync(this AsyncPolicy policy, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() - { - return policy.RaiseExceptionAsync(1, contextData, configureException, cancellationToken); - } + public static Task RaiseExceptionAsync(this AsyncPolicy policy, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() + { + return policy.RaiseExceptionAsync(1, contextData, configureException, cancellationToken); + } + } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs index 219bbcc64b7..04e172850bb 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensions.cs @@ -2,56 +2,57 @@ using System.Collections.Generic; using System.Linq; -namespace Polly.Specs.Helpers; - -public static class ContextualPolicyTResultExtensions +namespace Polly.Specs.Helpers { - public static TResult RaiseResultSequence(this Policy policy, - IDictionary contextData, - params TResult[] resultsToRaise) + public static class ContextualPolicyTResultExtensions { - return policy.RaiseResultSequence(contextData, resultsToRaise.ToList()); - } - - public static TResult RaiseResultSequence(this Policy policy, - IDictionary contextData, - IEnumerable resultsToRaise) - { - var enumerator = resultsToRaise.GetEnumerator(); - - return policy.Execute(_ => + public static TResult RaiseResultSequence(this Policy policy, + IDictionary contextData, + params TResult[] resultsToRaise) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } - - return enumerator.Current; - }, contextData); - } + return policy.RaiseResultSequence(contextData, resultsToRaise.ToList()); + } - public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, - IDictionary contextData, - params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceOnExecuteAndCapture(contextData, resultsToRaise.ToList()); - } + public static TResult RaiseResultSequence(this Policy policy, + IDictionary contextData, + IEnumerable resultsToRaise) + { + var enumerator = resultsToRaise.GetEnumerator(); - public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, - IDictionary contextData, - IEnumerable resultsToRaise) - { - var enumerator = resultsToRaise.GetEnumerator(); + return policy.Execute(_ => + { + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } + + return enumerator.Current; + }, contextData); + } + + public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, + IDictionary contextData, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceOnExecuteAndCapture(contextData, resultsToRaise.ToList()); + } - return policy.ExecuteAndCapture(_ => + public static PolicyResult RaiseResultSequenceOnExecuteAndCapture(this Policy policy, + IDictionary contextData, + IEnumerable resultsToRaise) { - if (!enumerator.MoveNext()) + var enumerator = resultsToRaise.GetEnumerator(); + + return policy.ExecuteAndCapture(_ => { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } - return enumerator.Current; - }, contextData); - } + return enumerator.Current; + }, contextData); + } -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs index 0e4caeccdbd..af6da4f44f6 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyTResultExtensionsAsync.cs @@ -4,50 +4,51 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Helpers; - -public static class ContextualPolicyTResultExtensionsAsync +namespace Polly.Specs.Helpers { - - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, - IDictionary contextData, - params TResult[] resultsToRaise) + public static class ContextualPolicyTResultExtensionsAsync { - return policy.RaiseResultSequenceAsync(contextData, CancellationToken.None, resultsToRaise.ToList()); - } - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IDictionary contextData, CancellationToken cancellationToken, IEnumerable resultsToRaise) - { - var enumerator = resultsToRaise.GetEnumerator(); + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, + IDictionary contextData, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAsync(contextData, CancellationToken.None, resultsToRaise.ToList()); + } - return policy.ExecuteAsync((_, _) => + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IDictionary contextData, CancellationToken cancellationToken, IEnumerable resultsToRaise) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } + var enumerator = resultsToRaise.GetEnumerator(); - return Task.FromResult(enumerator.Current); - }, contextData, cancellationToken); - } + return policy.ExecuteAsync((_, _) => + { + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } - public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceOnExecuteAndCaptureAsync(contextData, resultsToRaise.ToList()); - } + return Task.FromResult(enumerator.Current); + }, contextData, cancellationToken); + } - public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, IEnumerable resultsToRaise) - { - var enumerator = resultsToRaise.GetEnumerator(); + public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceOnExecuteAndCaptureAsync(contextData, resultsToRaise.ToList()); + } - return policy.ExecuteAndCaptureAsync(_ => + public static Task> RaiseResultSequenceOnExecuteAndCaptureAsync(this AsyncPolicy policy, IDictionary contextData, IEnumerable resultsToRaise) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); - } + var enumerator = resultsToRaise.GetEnumerator(); - return Task.FromResult(enumerator.Current); - }, contextData); + return policy.ExecuteAndCaptureAsync(_ => + { + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), "Not enough TResult values in resultsToRaise."); + } + + return Task.FromResult(enumerator.Current); + }, contextData); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs index df527aa2132..9b1267263ab 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs @@ -2,41 +2,42 @@ using System.Threading; using Polly.Utilities; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; - -internal static class AddBehaviourIfHandleEngine +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle { - internal static TResult Implementation( - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Action> behaviourIfHandle, - Func action, - Context context, - CancellationToken cancellationToken) + internal static class AddBehaviourIfHandleEngine { - try + internal static TResult Implementation( + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Action> behaviourIfHandle, + Func action, + Context context, + CancellationToken cancellationToken) { - var result = action(context, cancellationToken); - - if (shouldHandleResultPredicates.AnyMatch(result)) + try { - behaviourIfHandle(new DelegateResult(result)); - } + var result = action(context, cancellationToken); - return result; - } - catch (Exception ex) - { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; + if (shouldHandleResultPredicates.AnyMatch(result)) + { + behaviourIfHandle(new DelegateResult(result)); + } + + return result; } + catch (Exception ex) + { + var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - behaviourIfHandle(new DelegateResult(handledException)); + behaviourIfHandle(new DelegateResult(handledException)); - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); - throw; + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; + } } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs index 417d4f7f74e..71b5709b49a 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandlePolicy.cs @@ -1,55 +1,56 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; - -internal class AddBehaviourIfHandlePolicy : Policy +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle { - private readonly Action _behaviourIfHandle; - - internal AddBehaviourIfHandlePolicy(Action behaviourIfHandle, PolicyBuilder policyBuilder) - : base(policyBuilder) - { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); - } - - protected override TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken) + internal class AddBehaviourIfHandlePolicy : Policy { - return AddBehaviourIfHandleEngine.Implementation( - ExceptionPredicates, - ResultPredicates.None, - outcome => _behaviourIfHandle(outcome.Exception), - action, - context, - cancellationToken - ); - } -} + private readonly Action _behaviourIfHandle; -internal class AddBehaviourIfHandlePolicy : Policy -{ - private readonly Action> _behaviourIfHandle; + internal AddBehaviourIfHandlePolicy(Action behaviourIfHandle, PolicyBuilder policyBuilder) + : base(policyBuilder) + { + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + } - internal AddBehaviourIfHandlePolicy( - Action> behaviourIfHandle, - PolicyBuilder policyBuilder) - : base(policyBuilder) - { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + protected override TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken) + { + return AddBehaviourIfHandleEngine.Implementation( + ExceptionPredicates, + ResultPredicates.None, + outcome => _behaviourIfHandle(outcome.Exception), + action, + context, + cancellationToken + ); + } } - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + internal class AddBehaviourIfHandlePolicy : Policy { - return AddBehaviourIfHandleEngine.Implementation( - ExceptionPredicates, - ResultPredicates, - _behaviourIfHandle, - action, - context, - cancellationToken - ); + private readonly Action> _behaviourIfHandle; + + internal AddBehaviourIfHandlePolicy( + Action> behaviourIfHandle, + PolicyBuilder policyBuilder) + : base(policyBuilder) + { + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + } + + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return AddBehaviourIfHandleEngine.Implementation( + ExceptionPredicates, + ResultPredicates, + _behaviourIfHandle, + action, + context, + cancellationToken + ); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs index db810908e1b..38740e30877 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleSyntax.cs @@ -1,20 +1,21 @@ using System; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; - -internal static class AddBehaviourIfHandleSyntax +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle { - internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action behaviourIfHandle) + internal static class AddBehaviourIfHandleSyntax { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action behaviourIfHandle) + { + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); - } + return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + } - internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action> behaviourIfHandle) - { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + internal static AddBehaviourIfHandlePolicy WithBehaviour(this PolicyBuilder policyBuilder, Action> behaviourIfHandle) + { + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + return new AddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs index 736b9ab5e54..2178114eb3a 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs @@ -3,42 +3,43 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; - -internal static class AsyncAddBehaviourIfHandleEngine +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle { - internal static async Task ImplementationAsync( - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Func, Task> behaviourIfHandle, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) + internal static class AsyncAddBehaviourIfHandleEngine { - try + internal static async Task ImplementationAsync( + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Func, Task> behaviourIfHandle, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) { - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - - if (shouldHandleResultPredicates.AnyMatch(result)) + try { - await behaviourIfHandle(new DelegateResult(result)).ConfigureAwait(continueOnCapturedContext); - } + var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - return result; - } - catch (Exception ex) - { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; + if (shouldHandleResultPredicates.AnyMatch(result)) + { + await behaviourIfHandle(new DelegateResult(result)).ConfigureAwait(continueOnCapturedContext); + } + + return result; } + catch (Exception ex) + { + var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - await behaviourIfHandle(new DelegateResult(ex)).ConfigureAwait(continueOnCapturedContext); + await behaviourIfHandle(new DelegateResult(ex)).ConfigureAwait(continueOnCapturedContext); - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); - throw; + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; + } } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs index 37690792280..c5489d34cf1 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandlePolicy.cs @@ -1,59 +1,60 @@ using System; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; - -internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle { - private readonly Func _behaviourIfHandle; - - internal AsyncAddBehaviourIfHandlePolicy( - Func behaviourIfHandle, - PolicyBuilder policyBuilder) - : base(policyBuilder) + internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + private readonly Func _behaviourIfHandle; + + internal AsyncAddBehaviourIfHandlePolicy( + Func behaviourIfHandle, + PolicyBuilder policyBuilder) + : base(policyBuilder) + { + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + } + + protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( + ExceptionPredicates, + ResultPredicates.None, + outcome => _behaviourIfHandle(outcome.Exception), + action, + context, + cancellationToken, + continueOnCapturedContext + ); + } } - protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, - bool continueOnCapturedContext) + internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy { - return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( - ExceptionPredicates, - ResultPredicates.None, - outcome => _behaviourIfHandle(outcome.Exception), - action, - context, - cancellationToken, - continueOnCapturedContext - ); + private readonly Func, Task> _behaviourIfHandle; + + internal AsyncAddBehaviourIfHandlePolicy( + Func, Task> behaviourIfHandle, + PolicyBuilder policyBuilder) + : base(policyBuilder) + { + _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); + + } + + protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( + ExceptionPredicates, + ResultPredicates, + _behaviourIfHandle, + action, + context, + cancellationToken, + continueOnCapturedContext + ); + } } } - -internal class AsyncAddBehaviourIfHandlePolicy : AsyncPolicy -{ - private readonly Func, Task> _behaviourIfHandle; - - internal AsyncAddBehaviourIfHandlePolicy( - Func, Task> behaviourIfHandle, - PolicyBuilder policyBuilder) - : base(policyBuilder) - { - _behaviourIfHandle = behaviourIfHandle ?? throw new ArgumentNullException(nameof(behaviourIfHandle)); - - } - - protected override Task ImplementationAsync(Func> action, Context context, System.Threading.CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncAddBehaviourIfHandleEngine.ImplementationAsync( - ExceptionPredicates, - ResultPredicates, - _behaviourIfHandle, - action, - context, - cancellationToken, - continueOnCapturedContext - ); - } -} \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs index de71dca601f..d16499b4b5a 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleSyntax.cs @@ -1,25 +1,26 @@ using System; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle; - -internal static class AsyncAddBehaviourIfHandleSyntax +namespace Polly.Specs.Helpers.Custom.AddBehaviourIfHandle { - internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( - this PolicyBuilder policyBuilder, - Func behaviourIfHandle) + internal static class AsyncAddBehaviourIfHandleSyntax { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( + this PolicyBuilder policyBuilder, + Func behaviourIfHandle) + { + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); - } + return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + } - internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( - this PolicyBuilder policyBuilder, - Func, Task> behaviourIfHandle) - { - if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); + internal static AsyncAddBehaviourIfHandlePolicy WithBehaviourAsync( + this PolicyBuilder policyBuilder, + Func, Task> behaviourIfHandle) + { + if (behaviourIfHandle == null) throw new ArgumentNullException(nameof(behaviourIfHandle)); - return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + return new AsyncAddBehaviourIfHandlePolicy(behaviourIfHandle, policyBuilder); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs index 7842194937e..4f41915a540 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecuteEngine.cs @@ -2,31 +2,32 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.PreExecute; - -internal static class AsyncPreExecuteEngine +namespace Polly.Specs.Helpers.Custom.PreExecute { - internal static async Task ImplementationAsync( - Func preExecute, - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) + internal static class AsyncPreExecuteEngine { - await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); + internal static async Task ImplementationAsync( + Func preExecute, + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); - await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } - internal static async Task ImplementationAsync( - Func preExecute, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); + internal static async Task ImplementationAsync( + Func preExecute, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + await (preExecute?.Invoke() ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext); - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs index ff8e1694bde..15e49b2c35d 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/AsyncPreExecutePolicy.cs @@ -2,46 +2,47 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Helpers.Custom.PreExecute; - -internal class AsyncPreExecutePolicy : AsyncPolicy -{ - private Func _preExecute; - - public static AsyncPreExecutePolicy CreateAsync(Func preExecute) - { - return new AsyncPreExecutePolicy(preExecute); - } - - internal AsyncPreExecutePolicy(Func preExecute) - { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); - } - - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); - } -} - -internal class AsyncPreExecutePolicy : AsyncPolicy +namespace Polly.Specs.Helpers.Custom.PreExecute { - private Func _preExecute; - - public static AsyncPreExecutePolicy CreateAsync(Func preExecute) - { - return new AsyncPreExecutePolicy(preExecute); - } - - internal AsyncPreExecutePolicy(Func preExecute) + internal class AsyncPreExecutePolicy : AsyncPolicy { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + private Func _preExecute; + + public static AsyncPreExecutePolicy CreateAsync(Func preExecute) + { + return new AsyncPreExecutePolicy(preExecute); + } + + internal AsyncPreExecutePolicy(Func preExecute) + { + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } + + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + } } - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) + internal class AsyncPreExecutePolicy : AsyncPolicy { - return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + private Func _preExecute; + + public static AsyncPreExecutePolicy CreateAsync(Func preExecute) + { + return new AsyncPreExecutePolicy(preExecute); + } + + internal AsyncPreExecutePolicy(Func preExecute) + { + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } + + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncPreExecuteEngine.ImplementationAsync(_preExecute, action, context, cancellationToken, continueOnCapturedContext); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs index 49120e08efb..9a4a787d877 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecuteEngine.cs @@ -1,29 +1,30 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers.Custom.PreExecute; - -internal static class PreExecuteEngine +namespace Polly.Specs.Helpers.Custom.PreExecute { - internal static void Implementation( - Action preExecute, - Action action, - Context context, - CancellationToken cancellationToken) + internal static class PreExecuteEngine { - preExecute?.Invoke(); + internal static void Implementation( + Action preExecute, + Action action, + Context context, + CancellationToken cancellationToken) + { + preExecute?.Invoke(); - action(context, cancellationToken); - } + action(context, cancellationToken); + } - internal static TResult Implementation( - Action preExecute, - Func action, - Context context, - CancellationToken cancellationToken) - { - preExecute?.Invoke(); + internal static TResult Implementation( + Action preExecute, + Func action, + Context context, + CancellationToken cancellationToken) + { + preExecute?.Invoke(); - return action(context, cancellationToken); + return action(context, cancellationToken); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs index 0c6de5dbcec..d41d9c3719c 100644 --- a/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs +++ b/src/Polly.Specs/Helpers/Custom/PreExecute/PreExecutePolicy.cs @@ -1,44 +1,45 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers.Custom.PreExecute; - -internal class PreExecutePolicy : Policy -{ - private Action _preExecute; - - public static PreExecutePolicy Create(Action preExecute) - { - return new PreExecutePolicy(preExecute); - } - - internal PreExecutePolicy(Action preExecute) - { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); - } - - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); - } -} - -internal class PreExecutePolicy : Policy +namespace Polly.Specs.Helpers.Custom.PreExecute { - private Action _preExecute; - - public static PreExecutePolicy Create(Action preExecute) - { - return new PreExecutePolicy(preExecute); - } - - internal PreExecutePolicy(Action preExecute) + internal class PreExecutePolicy : Policy { - _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + private Action _preExecute; + + public static PreExecutePolicy Create(Action preExecute) + { + return new PreExecutePolicy(preExecute); + } + + internal PreExecutePolicy(Action preExecute) + { + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } + + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + } } - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + internal class PreExecutePolicy : Policy { - return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + private Action _preExecute; + + public static PreExecutePolicy Create(Action preExecute) + { + return new PreExecutePolicy(preExecute); + } + + internal PreExecutePolicy(Action preExecute) + { + _preExecute = preExecute ?? throw new ArgumentNullException(nameof(preExecute)); + } + + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return PreExecuteEngine.Implementation(_preExecute, action, context, cancellationToken); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/ObjectExtensions.cs b/src/Polly.Specs/Helpers/ObjectExtensions.cs index 7172601fa18..3f330295935 100644 --- a/src/Polly.Specs/Helpers/ObjectExtensions.cs +++ b/src/Polly.Specs/Helpers/ObjectExtensions.cs @@ -2,16 +2,17 @@ using System.Reflection; using System.Linq; -namespace Polly.Specs.Helpers; - -public static class ObjectExtensions +namespace Polly.Specs.Helpers { - public static IDictionary AsDictionary(this object source) + public static class ObjectExtensions { - return source.GetType().GetRuntimeProperties().ToDictionary - ( - propInfo => propInfo.Name, - propInfo => propInfo.GetValue(source, null) - ); + public static IDictionary AsDictionary(this object source) + { + return source.GetType().GetRuntimeProperties().ToDictionary + ( + propInfo => propInfo.Name, + propInfo => propInfo.GetValue(source, null) + ); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/PolicyExtensions.cs b/src/Polly.Specs/Helpers/PolicyExtensions.cs index 3c117a712fe..9c6feafd014 100644 --- a/src/Polly.Specs/Helpers/PolicyExtensions.cs +++ b/src/Polly.Specs/Helpers/PolicyExtensions.cs @@ -1,122 +1,123 @@ using System; using System.Threading; -namespace Polly.Specs.Helpers; - -public static class PolicyExtensions +namespace Polly.Specs.Helpers { - public class ExceptionAndOrCancellationScenario + public static class PolicyExtensions { - public int NumberOfTimesToRaiseException; - public int? AttemptDuringWhichToCancel; - public bool ActionObservesCancellation = true; - } + public class ExceptionAndOrCancellationScenario + { + public int NumberOfTimesToRaiseException; + public int? AttemptDuringWhichToCancel; + public bool ActionObservesCancellation = true; + } - public static void RaiseException(this Policy policy, TException instance) where TException : Exception - { - var scenario = new ExceptionAndOrCancellationScenario + public static void RaiseException(this Policy policy, TException instance) where TException : Exception { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = 1 - }; + var scenario = new ExceptionAndOrCancellationScenario + { + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = 1 + }; - policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, _ => instance); - } + policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, _ => instance); + } - public static void RaiseException(this Policy policy, Action configureException = null) where TException : Exception, new() - { - policy.RaiseException(1, configureException); - } - - public static void RaiseException(this Policy policy, int numberOfTimesToRaiseException, Action configureException = null) where TException : Exception, new() - { - var scenario = new ExceptionAndOrCancellationScenario + public static void RaiseException(this Policy policy, Action configureException = null) where TException : Exception, new() { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = numberOfTimesToRaiseException - }; + policy.RaiseException(1, configureException); + } - Func exceptionFactory = i => + public static void RaiseException(this Policy policy, int numberOfTimesToRaiseException, Action configureException = null) where TException : Exception, new() { - var exception = new TException(); - configureException?.Invoke(exception, i); - return exception; - }; - - policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); - } + var scenario = new ExceptionAndOrCancellationScenario + { + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = numberOfTimesToRaiseException + }; - public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() - { - policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, _ => new TException()); - } + Func exceptionFactory = i => + { + var exception = new TException(); + configureException?.Invoke(exception, i); + return exception; + }; - public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() - { - return policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, - _ => new TException(), successResult); - } + policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); + } - public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception - { - var counter = 0; + public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() + { + policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, _ => new TException()); + } - var cancellationToken = cancellationTokenSource.Token; + public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() + { + return policy.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, + _ => new TException(), successResult); + } - policy.Execute(ct => + public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception { - onExecute(); + var counter = 0; - counter++; + var cancellationToken = cancellationTokenSource.Token; - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + policy.Execute(ct => { - cancellationTokenSource.Cancel(); - } + onExecute(); - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + counter++; - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } - }, cancellationToken); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception - { - var counter = 0; + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - var cancellationToken = cancellationTokenSource.Token; + }, cancellationToken); + } - return policy.Execute(ct => + public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception { - onExecute(); + var counter = 0; - counter++; + var cancellationToken = cancellationTokenSource.Token; - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + return policy.Execute(ct => { - cancellationTokenSource.Cancel(); - } + onExecute(); - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + counter++; - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } + + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } + + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - return successResult; - }, cancellationToken); + return successResult; + }, cancellationToken); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs index b339599ae4b..715ecbf6c40 100644 --- a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs @@ -3,125 +3,126 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Specs.Helpers; - -public static class PolicyExtensionsAsync +namespace Polly.Specs.Helpers { - public class ExceptionAndOrCancellationScenario + public static class PolicyExtensionsAsync { - public int NumberOfTimesToRaiseException; + public class ExceptionAndOrCancellationScenario + { + public int NumberOfTimesToRaiseException; - public int? AttemptDuringWhichToCancel; + public int? AttemptDuringWhichToCancel; - public bool ActionObservesCancellation = true; - } + public bool ActionObservesCancellation = true; + } - public static Task RaiseExceptionAsync(this AsyncPolicy policy, TException instance) where TException : Exception - { - var scenario = new ExceptionAndOrCancellationScenario + public static Task RaiseExceptionAsync(this AsyncPolicy policy, TException instance) where TException : Exception { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = 1 - }; + var scenario = new ExceptionAndOrCancellationScenario + { + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = 1 + }; - return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, _ => instance); - } + return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, _ => instance); + } - public static Task RaiseExceptionAsync(this AsyncPolicy policy, Action configureException = null) where TException : Exception, new() - { - return policy.RaiseExceptionAsync(1, configureException); - } - - public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() - { - var scenario = new ExceptionAndOrCancellationScenario + public static Task RaiseExceptionAsync(this AsyncPolicy policy, Action configureException = null) where TException : Exception, new() { - ActionObservesCancellation = false, - AttemptDuringWhichToCancel = null, - NumberOfTimesToRaiseException = numberOfTimesToRaiseException - }; + return policy.RaiseExceptionAsync(1, configureException); + } - Func exceptionFactory = i => + public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() { - var exception = new TException(); - configureException?.Invoke(exception, i); - return exception; - }; - - return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); - } + var scenario = new ExceptionAndOrCancellationScenario + { + ActionObservesCancellation = false, + AttemptDuringWhichToCancel = null, + NumberOfTimesToRaiseException = numberOfTimesToRaiseException + }; - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() - { - return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, _ => new TException()); - } + Func exceptionFactory = i => + { + var exception = new TException(); + configureException?.Invoke(exception, i); + return exception; + }; - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() - { - return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - _ => new TException(), successResult); - } + return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, exceptionFactory); + } - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception - { - var counter = 0; + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new() + { + return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, _ => new TException()); + } - var cancellationToken = cancellationTokenSource.Token; + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new() + { + return policy.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + _ => new TException(), successResult); + } - return policy.ExecuteAsync(ct => + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception { - onExecute(); + var counter = 0; - counter++; + var cancellationToken = cancellationTokenSource.Token; - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + return policy.ExecuteAsync(ct => { - cancellationTokenSource.Cancel(); - } + onExecute(); - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + counter++; - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } - return TaskHelper.EmptyTask; - }, cancellationToken); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception - { - var counter = 0; + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - var cancellationToken = cancellationTokenSource.Token; + return TaskHelper.EmptyTask; + }, cancellationToken); + } - return policy.ExecuteAsync(ct => + public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception { - onExecute(); + var counter = 0; - counter++; + var cancellationToken = cancellationTokenSource.Token; - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + return policy.ExecuteAsync(ct => { - cancellationTokenSource.Cancel(); - } + onExecute(); - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + counter++; - if (counter <= scenario.NumberOfTimesToRaiseException) - { - throw exceptionFactory(counter); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } + + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } + + if (counter <= scenario.NumberOfTimesToRaiseException) + { + throw exceptionFactory(counter); + } - return Task.FromResult(successResult); - }, cancellationToken); + return Task.FromResult(successResult); + }, cancellationToken); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs index e8a48810cdf..83344379623 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs @@ -4,112 +4,114 @@ using System.Threading; using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.Helpers; - -public static class PolicyTResultExtensions +namespace Polly.Specs.Helpers { - public static TResult RaiseResultSequence(this Policy policy, params TResult[] resultsToRaise) + public static class PolicyTResultExtensions { - return policy.RaiseResultSequence(resultsToRaise.ToList()); - } + public static TResult RaiseResultSequence(this Policy policy, params TResult[] resultsToRaise) + { + return policy.RaiseResultSequence(resultsToRaise.ToList()); + } - public static TResult RaiseResultSequence(this Policy policy, IEnumerable resultsToRaise) - { - using (var enumerator = resultsToRaise.GetEnumerator()) + public static TResult RaiseResultSequence(this Policy policy, IEnumerable resultsToRaise) { - return policy.Execute(() => + using (var enumerator = resultsToRaise.GetEnumerator()) { - if (!enumerator.MoveNext()) + return policy.Execute(() => { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } - - return enumerator.Current; - }); + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } + + return enumerator.Current; + }); + } } - } - public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, params object[] resultsOrExceptionsToRaise) - { - return policy.RaiseResultAndOrExceptionSequence(resultsOrExceptionsToRaise.ToList()); - } - - public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, - IEnumerable resultsOrExceptionsToRaise) - { - using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) + public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, params object[] resultsOrExceptionsToRaise) { + return policy.RaiseResultAndOrExceptionSequence(resultsOrExceptionsToRaise.ToList()); + } - return policy.Execute(() => + public static TResult RaiseResultAndOrExceptionSequence(this Policy policy, + IEnumerable resultsOrExceptionsToRaise) + { + using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) { - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); - } - var current = enumerator.Current; - if (current is Exception) - { - throw (Exception) current; - } - else if (current is TResult) + return policy.Execute(() => { - return (TResult) current; - } - else - { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); - } - }); + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); + } + + var current = enumerator.Current; + if (current is Exception) + { + throw (Exception) current; + } + else if (current is TResult) + { + return (TResult) current; + } + else + { + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); + } + }); + } } - } - public class ResultAndOrCancellationScenario - { - public int? AttemptDuringWhichToCancel = null; + public class ResultAndOrCancellationScenario + { + public int? AttemptDuringWhichToCancel = null; - public bool ActionObservesCancellation = true; - } + public bool ActionObservesCancellation = true; + } - public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, - Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, - params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - resultsToRaise.ToList()); - } + public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, + Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + resultsToRaise.ToList()); + } - public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, IEnumerable resultsToRaise) - { - var counter = 0; + public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, IEnumerable resultsToRaise) + { + var counter = 0; - var cancellationToken = cancellationTokenSource.Token; + var cancellationToken = cancellationTokenSource.Token; - using (var enumerator = resultsToRaise.GetEnumerator()) - { - return policy.Execute(ct => + using (var enumerator = resultsToRaise.GetEnumerator()) { - onExecute(); + return policy.Execute(ct => + { + onExecute(); - counter++; + counter++; - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - return enumerator.Current; - }, cancellationToken); + return enumerator.Current; + }, cancellationToken); + } } } -} \ No newline at end of file +} + diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs index 91b179ff5ad..11bf1131d48 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs @@ -6,126 +6,127 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.Helpers; - -public static class PolicyTResultExtensionsAsync +namespace Polly.Specs.Helpers { - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, params TResult[] resultsToRaise) + public static class PolicyTResultExtensionsAsync { - return policy.RaiseResultSequenceAsync(resultsToRaise.ToList()); - } + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAsync(resultsToRaise.ToList()); + } - public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IEnumerable resultsToRaise) - { - return policy.RaiseResultSequenceAsync(default, resultsToRaise); - } + public static Task RaiseResultSequenceAsync(this AsyncPolicy policy, IEnumerable resultsToRaise) + { + return policy.RaiseResultSequenceAsync(default, resultsToRaise); + } - public static async Task RaiseResultSequenceAsync(this AsyncPolicy policy, - CancellationToken cancellationToken, IEnumerable resultsToRaise) - { - using (var enumerator = resultsToRaise.GetEnumerator()) + public static async Task RaiseResultSequenceAsync(this AsyncPolicy policy, + CancellationToken cancellationToken, IEnumerable resultsToRaise) { - return await policy.ExecuteAsync(_ => + using (var enumerator = resultsToRaise.GetEnumerator()) { - if (!enumerator.MoveNext()) + return await policy.ExecuteAsync(_ => { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } - - return Task.FromResult(enumerator.Current); - }, cancellationToken); + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } + + return Task.FromResult(enumerator.Current); + }, cancellationToken); + } } - } - public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, params object[] resultsOrExceptionsToRaise) - { - return policy.RaiseResultAndOrExceptionSequenceAsync(resultsOrExceptionsToRaise.ToList()); - } + public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, params object[] resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequenceAsync(resultsOrExceptionsToRaise.ToList()); + } - public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, - IEnumerable resultsOrExceptionsToRaise) - { - return policy.RaiseResultAndOrExceptionSequenceAsync(CancellationToken.None, resultsOrExceptionsToRaise); - } + public static Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, + IEnumerable resultsOrExceptionsToRaise) + { + return policy.RaiseResultAndOrExceptionSequenceAsync(CancellationToken.None, resultsOrExceptionsToRaise); + } - public static async Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, - CancellationToken cancellationToken, IEnumerable resultsOrExceptionsToRaise) - { - using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) + public static async Task RaiseResultAndOrExceptionSequenceAsync(this AsyncPolicy policy, + CancellationToken cancellationToken, IEnumerable resultsOrExceptionsToRaise) { - return await policy.ExecuteAsync(_ => + using (var enumerator = resultsOrExceptionsToRaise.GetEnumerator()) { - if (!enumerator.MoveNext()) + return await policy.ExecuteAsync(_ => { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); - } - - var current = enumerator.Current; - if (current is Exception) - { - throw (Exception) current; - } - else if (current is TResult) - { - return Task.FromResult((TResult) current); - } - else - { - throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), - $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); - } - }, cancellationToken); + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsOrExceptionsToRaise)}."); + } + + var current = enumerator.Current; + if (current is Exception) + { + throw (Exception) current; + } + else if (current is TResult) + { + return Task.FromResult((TResult) current); + } + else + { + throw new ArgumentOutOfRangeException(nameof(resultsOrExceptionsToRaise), + $"Value is not either an {typeof(Exception).Name} or {typeof(TResult).Name}."); + } + }, cancellationToken); + } } - } - public class ResultAndOrCancellationScenario - { - public int? AttemptDuringWhichToCancel = null; + public class ResultAndOrCancellationScenario + { + public int? AttemptDuringWhichToCancel = null; - public bool ActionObservesCancellation = true; - } + public bool ActionObservesCancellation = true; + } - public static Task RaiseResultSequenceAndOrCancellationAsync(this AsyncPolicy policy, - Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, - params TResult[] resultsToRaise) - { - return policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - resultsToRaise.ToList()); - } + public static Task RaiseResultSequenceAndOrCancellationAsync(this AsyncPolicy policy, + Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, + params TResult[] resultsToRaise) + { + return policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + resultsToRaise.ToList()); + } - public static async Task RaiseResultSequenceAndOrCancellationAsync( - this AsyncPolicy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, - Action onExecute, IEnumerable resultsToRaise) - { - var counter = 0; + public static async Task RaiseResultSequenceAndOrCancellationAsync( + this AsyncPolicy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, + Action onExecute, IEnumerable resultsToRaise) + { + var counter = 0; - var cancellationToken = cancellationTokenSource.Token; + var cancellationToken = cancellationTokenSource.Token; - using (var enumerator = resultsToRaise.GetEnumerator()) - { - return await policy.ExecuteAsync(ct => + using (var enumerator = resultsToRaise.GetEnumerator()) { - onExecute(); + return await policy.ExecuteAsync(ct => + { + onExecute(); - counter++; + counter++; - if (!enumerator.MoveNext()) - { - throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); - } + if (!enumerator.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(resultsToRaise), $"Not enough {typeof(TResult).Name} values in {nameof(resultsToRaise)}."); + } - if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) - { - cancellationTokenSource.Cancel(); - } + if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value) + { + cancellationTokenSource.Cancel(); + } - if (scenario.ActionObservesCancellation) - { - ct.ThrowIfCancellationRequested(); - } + if (scenario.ActionObservesCancellation) + { + ct.ThrowIfCancellationRequested(); + } - return Task.FromResult(enumerator.Current); - }, cancellationToken); + return Task.FromResult(enumerator.Current); + }, cancellationToken); + } } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs b/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs index 709ea478b37..7a5bfaf17d0 100644 --- a/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs +++ b/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs @@ -2,38 +2,39 @@ using FluentAssertions; using Polly.RateLimit; -namespace Polly.Specs.Helpers.RateLimit; - -internal static class IRateLimiterExtensions +namespace Polly.Specs.Helpers.RateLimit { - public static void ShouldPermitAnExecution(this IRateLimiter rateLimiter) - { - var canExecute = rateLimiter.PermitExecution(); - - canExecute.permitExecution.Should().BeTrue(); - canExecute.retryAfter.Should().Be(TimeSpan.Zero); - } - - public static void ShouldPermitNExecutions(this IRateLimiter rateLimiter, long numberOfExecutions) + internal static class IRateLimiterExtensions { - for (var execution = 0; execution < numberOfExecutions; execution++) + public static void ShouldPermitAnExecution(this IRateLimiter rateLimiter) { - rateLimiter.ShouldPermitAnExecution(); - } - } + var canExecute = rateLimiter.PermitExecution(); - public static void ShouldNotPermitAnExecution(this IRateLimiter rateLimiter, TimeSpan? retryAfter = null) - { - var canExecute = rateLimiter.PermitExecution(); + canExecute.permitExecution.Should().BeTrue(); + canExecute.retryAfter.Should().Be(TimeSpan.Zero); + } - canExecute.permitExecution.Should().BeFalse(); - if (retryAfter == null) + public static void ShouldPermitNExecutions(this IRateLimiter rateLimiter, long numberOfExecutions) { - canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); + for (var execution = 0; execution < numberOfExecutions; execution++) + { + rateLimiter.ShouldPermitAnExecution(); + } } - else + + public static void ShouldNotPermitAnExecution(this IRateLimiter rateLimiter, TimeSpan? retryAfter = null) { - canExecute.retryAfter.Should().Be(retryAfter.Value); + var canExecute = rateLimiter.PermitExecution(); + + canExecute.permitExecution.Should().BeFalse(); + if (retryAfter == null) + { + canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); + } + else + { + canExecute.retryAfter.Should().Be(retryAfter.Value); + } } } } \ No newline at end of file diff --git a/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs b/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs index 0664075caf9..fcb1c72c46d 100644 --- a/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs +++ b/src/Polly.Specs/Helpers/RateLimit/ResultClassWithRetryAfter.cs @@ -1,20 +1,21 @@ using System; -namespace Polly.Specs.Helpers.RateLimit; - -internal class ResultClassWithRetryAfter : ResultClass +namespace Polly.Specs.Helpers.RateLimit { - public TimeSpan RetryAfter { get; } + internal class ResultClassWithRetryAfter : ResultClass + { + public TimeSpan RetryAfter { get; } - public ResultClassWithRetryAfter(ResultPrimitive result) + public ResultClassWithRetryAfter(ResultPrimitive result) : base(result) - { - RetryAfter = TimeSpan.Zero; - } + { + RetryAfter = TimeSpan.Zero; + } - public ResultClassWithRetryAfter(TimeSpan retryAfter) + public ResultClassWithRetryAfter(TimeSpan retryAfter) : base(ResultPrimitive.Undefined) - { - RetryAfter = retryAfter; + { + RetryAfter = retryAfter; + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Helpers/ResultClass.cs b/src/Polly.Specs/Helpers/ResultClass.cs index 075617578af..e07ed99eed5 100644 --- a/src/Polly.Specs/Helpers/ResultClass.cs +++ b/src/Polly.Specs/Helpers/ResultClass.cs @@ -1,21 +1,22 @@ -namespace Polly.Specs.Helpers; - -/// -/// A helper class supporting tests on how Policy<TResult> policies handle return results which are class types (as opposed to primitive types). -/// -internal class ResultClass +namespace Polly.Specs.Helpers { - public ResultClass(ResultPrimitive resultCode) + /// + /// A helper class supporting tests on how Policy<TResult> policies handle return results which are class types (as opposed to primitive types). + /// + internal class ResultClass { - ResultCode = resultCode; - } + public ResultClass(ResultPrimitive resultCode) + { + ResultCode = resultCode; + } - public ResultClass(ResultPrimitive resultCode, string someString) : this(resultCode) - { - SomeString = someString; - } + public ResultClass(ResultPrimitive resultCode, string someString) : this(resultCode) + { + SomeString = someString; + } - public ResultPrimitive ResultCode { get; set; } + public ResultPrimitive ResultCode { get; set; } - public string SomeString { get; set; } -} \ No newline at end of file + public string SomeString { get; set; } + } +} diff --git a/src/Polly.Specs/Helpers/ResultPrimitive.cs b/src/Polly.Specs/Helpers/ResultPrimitive.cs index 8b3b1c911c2..d605b2aad8a 100644 --- a/src/Polly.Specs/Helpers/ResultPrimitive.cs +++ b/src/Polly.Specs/Helpers/ResultPrimitive.cs @@ -1,16 +1,17 @@ -namespace Polly.Specs.Helpers; - -/// -/// A helper class supporting tests on how Policy<TResult> policies may handle return results which are primitive types such as ints or enums. -/// -internal enum ResultPrimitive +namespace Polly.Specs.Helpers { - Undefined, - Fault, - Good, - FaultAgain, - GoodAgain, - FaultYetAgain, - Substitute, - WhateverButTooLate -} \ No newline at end of file + /// + /// A helper class supporting tests on how Policy<TResult> policies may handle return results which are primitive types such as ints or enums. + /// + internal enum ResultPrimitive + { + Undefined, + Fault, + Good, + FaultAgain, + GoodAgain, + FaultYetAgain, + Substitute, + WhateverButTooLate + } +} diff --git a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs index cf167bbb0fc..639eab61e60 100644 --- a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs +++ b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs @@ -5,31 +5,33 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs; - -public class IAsyncPolicyExtensionsSpecs +namespace Polly.Specs { - [Fact] - public void Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_a_generic_IAsyncPolicyTResult() + public class IAsyncPolicyExtensionsSpecs { - IAsyncPolicy nonGenericPolicy = Policy.TimeoutAsync(10); - var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); + [Fact] + public void Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_a_generic_IAsyncPolicyTResult() + { + IAsyncPolicy nonGenericPolicy = Policy.TimeoutAsync(10); + var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); - genericPolicy.Should().BeAssignableTo>(); - } + genericPolicy.Should().BeAssignableTo>(); + } - [Fact] - public async Task Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_an_IAsyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() - { - // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. + [Fact] + public async Task Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_an_IAsyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() + { + // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero); - IAsyncPolicy nonGenericPolicy = breaker; - var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); - var deleg = () => Task.FromResult(ResultPrimitive.Good); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero); + IAsyncPolicy nonGenericPolicy = breaker; + var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); + var deleg = () => Task.FromResult(ResultPrimitive.Good); - (await genericPolicy.ExecuteAsync(deleg)).Should().Be(ResultPrimitive.Good); - breaker.Isolate(); - genericPolicy.Awaiting(p => p.ExecuteAsync(deleg)).Should().Throw(); + (await genericPolicy.ExecuteAsync(deleg)).Should().Be(ResultPrimitive.Good); + breaker.Isolate(); + genericPolicy.Awaiting(p => p.ExecuteAsync(deleg)).Should().Throw(); + } } -} \ No newline at end of file + +} diff --git a/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs b/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs index 5b946f62659..37b7c1b7b04 100644 --- a/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs +++ b/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs @@ -4,31 +4,33 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs; - -public class ISyncPolicyExtensionsSpecs +namespace Polly.Specs { - [Fact] - public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_a_generic_ISyncPolicyTResult() + public class ISyncPolicyExtensionsSpecs { - ISyncPolicy nonGenericPolicy = Policy.Timeout(10); - var genericPolicy = nonGenericPolicy.AsPolicy(); + [Fact] + public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_a_generic_ISyncPolicyTResult() + { + ISyncPolicy nonGenericPolicy = Policy.Timeout(10); + var genericPolicy = nonGenericPolicy.AsPolicy(); - genericPolicy.Should().BeAssignableTo>(); - } + genericPolicy.Should().BeAssignableTo>(); + } - [Fact] - public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_an_ISyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() - { - // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. + [Fact] + public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_an_ISyncPolicyTResult_version_of_the_supplied_nongeneric_policy_instance() + { + // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - ISyncPolicy nonGenericPolicy = breaker; - var genericPolicy = nonGenericPolicy.AsPolicy(); - var deleg = () => ResultPrimitive.Good; + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + ISyncPolicy nonGenericPolicy = breaker; + var genericPolicy = nonGenericPolicy.AsPolicy(); + var deleg = () => ResultPrimitive.Good; - genericPolicy.Execute(deleg).Should().Be(ResultPrimitive.Good); - breaker.Isolate(); - genericPolicy.Invoking(p => p.Execute(deleg)).Should().Throw(); + genericPolicy.Execute(deleg).Should().Be(ResultPrimitive.Good); + breaker.Isolate(); + genericPolicy.Invoking(p => p.Execute(deleg)).Should().Throw(); + } } -} \ No newline at end of file + +} diff --git a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs index 247b3a9626f..a8c0535550a 100644 --- a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs @@ -3,38 +3,39 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.NoOp; - -public class NoOpAsyncSpecs +namespace Polly.Specs.NoOp { - [Fact] - public void Should_execute_user_delegate() + public class NoOpAsyncSpecs { - var policy = Policy.NoOpAsync(); - var executed = false; + [Fact] + public void Should_execute_user_delegate() + { + var policy = Policy.NoOpAsync(); + var executed = false; - policy.Awaiting(p => p.ExecuteAsync(() => { executed = true; return TaskHelper.EmptyTask; })) - .Should().NotThrow(); + policy.Awaiting(p => p.ExecuteAsync(() => { executed = true; return TaskHelper.EmptyTask; })) + .Should().NotThrow(); - executed.Should().BeTrue(); - } + executed.Should().BeTrue(); + } - [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - var policy = Policy.NoOpAsync(); + [Fact] + public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + { + var policy = Policy.NoOpAsync(); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync( + policy.Awaiting(p => p.ExecuteAsync( _ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) - .Should().NotThrow(); - } + .Should().NotThrow(); + } - executed.Should().BeTrue(); + executed.Should().BeTrue(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/NoOp/NoOpSpecs.cs b/src/Polly.Specs/NoOp/NoOpSpecs.cs index 28fae7d4ab1..ae844529b13 100644 --- a/src/Polly.Specs/NoOp/NoOpSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpSpecs.cs @@ -3,36 +3,37 @@ using Polly.NoOp; using Xunit; -namespace Polly.Specs.NoOp; - -public class NoOpSpecs +namespace Polly.Specs.NoOp { - [Fact] - public void Should_execute_user_delegate() + public class NoOpSpecs { - var policy = Policy.NoOp(); - var executed = false; - - policy.Invoking(x => x.Execute(() => { executed = true; })) - .Should().NotThrow(); + [Fact] + public void Should_execute_user_delegate() + { + var policy = Policy.NoOp(); + var executed = false; - executed.Should().BeTrue(); - } + policy.Invoking(x => x.Execute(() => { executed = true; })) + .Should().NotThrow(); - [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - var policy = Policy.NoOp(); - var executed = false; + executed.Should().BeTrue(); + } - using (var cts = new CancellationTokenSource()) + [Fact] + public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() { - cts.Cancel(); + var policy = Policy.NoOp(); + var executed = false; - policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) - .Should().NotThrow(); - } + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - executed.Should().BeTrue(); + policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) + .Should().NotThrow(); + } + + executed.Should().BeTrue(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs index f6f28fd882e..3f600e4c925 100644 --- a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs @@ -5,40 +5,41 @@ using Polly.NoOp; using Xunit; -namespace Polly.Specs.NoOp; - -public class NoOpTResultAsyncSpecs +namespace Polly.Specs.NoOp { - [Fact] - public void Should_execute_user_delegate() + public class NoOpTResultAsyncSpecs { - var policy = Policy.NoOpAsync(); - int? result = null; - - Func, Task> action = async p => result = await p.ExecuteAsync(() => Task.FromResult((int?)10)); - policy.Awaiting(action) - .Should().NotThrow(); - - result.HasValue.Should().BeTrue(); - result.Should().Be(10); - } - - [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - var policy = Policy.NoOpAsync(); - int? result = null; - - using (var cts = new CancellationTokenSource()) + [Fact] + public void Should_execute_user_delegate() { - cts.Cancel(); + var policy = Policy.NoOpAsync(); + int? result = null; - Func, Task> action = async p => result = await p.ExecuteAsync(_ => Task.FromResult((int?)10), cts.Token); + Func, Task> action = async p => result = await p.ExecuteAsync(() => Task.FromResult((int?)10)); policy.Awaiting(action) .Should().NotThrow(); + + result.HasValue.Should().BeTrue(); + result.Should().Be(10); } - result.HasValue.Should().BeTrue(); - result.Should().Be(10); + [Fact] + public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() + { + var policy = Policy.NoOpAsync(); + int? result = null; + + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); + + Func, Task> action = async p => result = await p.ExecuteAsync(_ => Task.FromResult((int?)10), cts.Token); + policy.Awaiting(action) + .Should().NotThrow(); + } + + result.HasValue.Should().BeTrue(); + result.Should().Be(10); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs b/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs index 5e8aacbd533..2d3bf38cc51 100644 --- a/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs @@ -3,38 +3,39 @@ using Polly.NoOp; using Xunit; -namespace Polly.Specs.NoOp; - -public class NoOpTResultSpecs +namespace Polly.Specs.NoOp { - [Fact] - public void Should_execute_user_delegate() + public class NoOpTResultSpecs { - var policy = Policy.NoOp(); - int? result = null; - - policy.Invoking(x => result = x.Execute(() => 10)) - .Should().NotThrow(); + [Fact] + public void Should_execute_user_delegate() + { + var policy = Policy.NoOp(); + int? result = null; - result.HasValue.Should().BeTrue(); - result.Should().Be(10); - } + policy.Invoking(x => result = x.Execute(() => 10)) + .Should().NotThrow(); - [Fact] - public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() - { - var policy = Policy.NoOp(); - int? result = null; + result.HasValue.Should().BeTrue(); + result.Should().Be(10); + } - using (var cts = new CancellationTokenSource()) + [Fact] + public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() { - cts.Cancel(); + var policy = Policy.NoOp(); + int? result = null; - policy.Invoking(p => result = p.Execute(_ => 10, cts.Token)) - .Should().NotThrow(); - } + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - result.HasValue.Should().BeTrue(); - result.Should().Be(10); + policy.Invoking(p => result = p.Execute(_ => 10, cts.Token)) + .Should().NotThrow(); + } + + result.HasValue.Should().BeTrue(); + result.Should().Be(10); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/PolicyAsyncSpecs.cs b/src/Polly.Specs/PolicyAsyncSpecs.cs index 7d5d2f1c2de..a07b55bfeaa 100644 --- a/src/Polly.Specs/PolicyAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyAsyncSpecs.cs @@ -5,296 +5,297 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs; - -public class PolicyAsyncSpecs +namespace Polly.Specs { - #region Execute tests - - [Fact] - public async Task Executing_the_policy_action_should_execute_the_specified_async_action() + public class PolicyAsyncSpecs { - var executed = false; - - var policy = Policy - .Handle() - .RetryAsync((_, _) => { }); + #region Execute tests - await policy.ExecuteAsync(() => + [Fact] + public async Task Executing_the_policy_action_should_execute_the_specified_async_action() { - executed = true; - return TaskHelper.EmptyTask; - }); - - executed.Should() - .BeTrue(); - } - - [Fact] - public async Task Executing_the_policy_function_should_execute_the_specified_async_function_and_return_the_result() - { - var policy = Policy - .Handle() - .RetryAsync((_, _) => { }); - - var result = await policy.ExecuteAsync(() => Task.FromResult(2)); - - result.Should() - .Be(2); - } + var executed = false; - #endregion + var policy = Policy + .Handle() + .RetryAsync((_, _) => { }); - #region ExecuteAndCapture tests + await policy.ExecuteAsync(() => + { + executed = true; + return TaskHelper.EmptyTask; + }); - [Fact] - public async Task Executing_the_policy_action_successfully_should_return_success_result() - { - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => TaskHelper.EmptyTask); + executed.Should() + .BeTrue(); + } - result.Should().BeEquivalentTo(new + [Fact] + public async Task Executing_the_policy_function_should_execute_the_specified_async_function_and_return_the_result() { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - }); - } + var policy = Policy + .Handle() + .RetryAsync((_, _) => { }); - [Fact] - public async Task Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() - { - var handledException = new DivideByZeroException(); + var result = await policy.ExecuteAsync(() => Task.FromResult(2)); - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw handledException); + result.Should() + .Be(2); + } - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy, - }); - } - - [Fact] - public async Task Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); + #endregion - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw unhandledException); + #region ExecuteAndCapture tests - result.Should().BeEquivalentTo(new + [Fact] + public async Task Executing_the_policy_action_successfully_should_return_success_result() { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled - }); - } - - [Fact] - public async Task Executing_the_policy_function_successfully_should_return_success_result() - { - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(Int32.MaxValue)); - - result.Should().BeEquivalentTo(new + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => TaskHelper.EmptyTask); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + }); + } + + [Fact] + public async Task Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(int), - Result = Int32.MaxValue - }); - } - - [Fact] - public async Task Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() - { - var handledException = new DivideByZeroException(); - - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw handledException); - - result.Should().BeEquivalentTo(new + var handledException = new DivideByZeroException(); + + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw handledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy, + }); + } + + [Fact] + public async Task Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy, - FaultType = FaultType.ExceptionHandledByThisPolicy, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - [Fact] - public async Task Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); - - var result = await Policy - .Handle() - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => throw unhandledException); - - result.Should().BeEquivalentTo(new + var unhandledException = new Exception(); + + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw unhandledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled + }); + } + + [Fact] + public async Task Executing_the_policy_function_successfully_should_return_success_result() { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled, - FaultType = FaultType.UnhandledException, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - #endregion - - #region Context tests + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(Int32.MaxValue)); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(int), + Result = Int32.MaxValue + }); + } + + [Fact] + public async Task Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() + { + var handledException = new DivideByZeroException(); + + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw handledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy, + FaultType = FaultType.ExceptionHandledByThisPolicy, + FinalHandledResult = default(int), + Result = default(int) + }); + } + + [Fact] + public async Task Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() + { + var unhandledException = new Exception(); + + var result = await Policy + .Handle() + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => throw unhandledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled, + FaultType = FaultType.UnhandledException, + FinalHandledResult = default(int), + Result = default(int) + }); + } + + #endregion + + #region Context tests + + [Fact] + public void Executing_the_policy_action_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Executing_the_policy_action_should_throw_when_context_data_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) + .Should().Throw(); + } - policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Executing_the_policy_action_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Executing_the_policy_action_should_throw_when_context_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Awaiting(p => p.ExecuteAsync(_ => TaskHelper.EmptyTask, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (IDictionary)null)) + .Should().Throw(); + } - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(2), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + await policy.ExecuteAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); - await policy.ExecuteAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) + .Should().Throw(); + } - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (IDictionary)null)) + .Should().Throw(); + } - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .Handle() + .RetryAsync((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - var policy = Policy - .Handle() - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(2), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); - await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return TaskHelper.EmptyTask; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + (await policy.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, executionContext)) + .Context.Should().BeSameAs(executionContext); + } - (await policy.ExecuteAndCaptureAsync(_ => TaskHelper.EmptyTask, executionContext)) - .Context.Should().BeSameAs(executionContext); + #endregion } - - #endregion -} \ No newline at end of file +} diff --git a/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs b/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs index 3a64bb76041..bd2703adbb0 100644 --- a/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs @@ -5,324 +5,325 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs; - -public class PolicyKeyAsyncSpecs +namespace Polly.Specs { - #region Configuration - - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() + public class PolicyKeyAsyncSpecs { - var policy = Policy.Handle().RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); + #region Configuration - policy.Should().BeAssignableTo(); - } + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() + { + var policy = Policy.Handle().RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - IAsyncPolicy policyAsInterface = Policy.Handle().RetryAsync(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); + policy.Should().BeAssignableTo(); + } - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); - } + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + IAsyncPolicy policyAsInterface = Policy.Handle().RetryAsync(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); + } - var policy = Policy.Handle().RetryAsync().WithPolicyKey(key); + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - policy.PolicyKey.Should().Be(key); - } + var policy = Policy.Handle().RetryAsync().WithPolicyKey(key); - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.Handle().RetryAsync(); + policy.PolicyKey.Should().Be(key); + } - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.Handle().RetryAsync(); - configure.Should().NotThrow(); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().NotThrow(); - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.Handle().RetryAsync(); + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.Handle().RetryAsync(); - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.Handle().RetryAsync(); + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } + + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.Handle().RetryAsync(); - policy.PolicyKey.Should().StartWith("AsyncRetry"); - } + policy.PolicyKey.Should().StartWith("AsyncRetry"); + } - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.Handle().RetryAsync(); - var policy2 = Policy.Handle().RetryAsync(); + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.Handle().RetryAsync(); + var policy2 = Policy.Handle().RetryAsync(); - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.Handle().RetryAsync(); + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.Handle().RetryAsync(); - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.Handle().RetryAsync(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.Handle().RetryAsync(); - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - #endregion + #endregion - #region PolicyKey and execution Context tests + #region PolicyKey and execution Context tests - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context() - { - var policyKey = Guid.NewGuid().ToString(); + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context() + { + var policyKey = Guid.NewGuid().ToString(); - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); - await retry.RaiseExceptionAsync(1); + await retry.RaiseExceptionAsync(1); - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - [Fact] - public async Task Should_pass_OperationKey_to_execution_context() - { - var operationKey = "SomeKey"; + [Fact] + public async Task Should_pass_OperationKey_to_execution_context() + { + var operationKey = "SomeKey"; - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry); + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry); - var firstExecution = true; - await retry.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - if (firstExecution) + var firstExecution = true; + await retry.ExecuteAsync(async _ => { - firstExecution = false; - throw new Exception(); - } - }, new Context(operationKey)); + await TaskHelper.EmptyTask; + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + }, new Context(operationKey)); + + operationKeySetOnContext.Should().Be(operationKey); + } + + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() + { + var policyKey = Guid.NewGuid().ToString(); - operationKeySetOnContext.Should().Be(operationKey); - } + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() - { - var policyKey = Guid.NewGuid().ToString(); + var firstExecution = true; + await retry.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + return 0; + }); + + policyKeySetOnExecutionContext.Should().Be(policyKey); + } + + [Fact] + public async Task Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() + { + var operationKey = "SomeKey"; - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().RetryAsync(1, onRetry); - var firstExecution = true; - await retry.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; - if (firstExecution) + var firstExecution = true; + await retry.ExecuteAsync(async _ => { - firstExecution = false; - throw new Exception(); - } - return 0; - }); + await TaskHelper.EmptyTask; + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + return 0; + }, new Context(operationKey)); + + operationKeySetOnContext.Should().Be(operationKey); + } + #endregion - policyKeySetOnExecutionContext.Should().Be(policyKey); } - [Fact] - public async Task Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() + public class PolicyTResultKeyAsyncSpecs { - var operationKey = "SomeKey"; + #region Configuration - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().RetryAsync(1, onRetry); - - var firstExecution = true; - await retry.ExecuteAsync(async _ => + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() { - await TaskHelper.EmptyTask; - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - return 0; - }, new Context(operationKey)); + var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); - operationKeySetOnContext.Should().Be(operationKey); - } - #endregion - -} + policy.Should().BeAssignableTo>(); + } -public class PolicyTResultKeyAsyncSpecs -{ - #region Configuration - - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() - { - var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + IAsyncPolicy policyAsInterface = Policy.HandleResult(0).RetryAsync(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - policy.Should().BeAssignableTo>(); - } + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); + } - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - IAsyncPolicy policyAsInterface = Policy.HandleResult(0).RetryAsync(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); - } + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(key); - var policy = Policy.HandleResult(0).RetryAsync().WithPolicyKey(key); + policy.PolicyKey.Should().Be(key); + } - policy.PolicyKey.Should().Be(key); - } - - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.HandleResult(0).RetryAsync(); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().NotThrow(); + configure.Should().NotThrow(); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).RetryAsync(); - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).RetryAsync(); - policy.PolicyKey.Should().StartWith("AsyncRetry"); - } + policy.PolicyKey.Should().StartWith("AsyncRetry"); + } - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.HandleResult(0).RetryAsync(); - var policy2 = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.HandleResult(0).RetryAsync(); + var policy2 = Policy.HandleResult(0).RetryAsync(); - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).RetryAsync(); - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.HandleResult(0).RetryAsync(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.HandleResult(0).RetryAsync(); - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - #endregion + #endregion - #region PolicyKey and execution Context tests + #region PolicyKey and execution Context tests - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context() - { - var policyKey = Guid.NewGuid().ToString(); + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context() + { + var policyKey = Guid.NewGuid().ToString(); - string policyKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(policyKey); + string policyKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(policyKey); - await retry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + await retry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - [Fact] - public async Task Should_pass_OperationKey_to_execution_context() - { - var operationKey = "SomeKey"; + [Fact] + public async Task Should_pass_OperationKey_to_execution_context() + { + var operationKey = "SomeKey"; - string operationKeySetOnContext = null; - Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry); + string operationKeySetOnContext = null; + Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry); - var firstExecution = true; - await retry.ExecuteAsync(async _ => - { - await TaskHelper.EmptyTask; - if (firstExecution) + var firstExecution = true; + await retry.ExecuteAsync(async _ => { - firstExecution = false; - return ResultPrimitive.Fault; - } - return ResultPrimitive.Good; - }, new Context(operationKey)); + await TaskHelper.EmptyTask; + if (firstExecution) + { + firstExecution = false; + return ResultPrimitive.Fault; + } + return ResultPrimitive.Good; + }, new Context(operationKey)); - operationKeySetOnContext.Should().Be(operationKey); - } + operationKeySetOnContext.Should().Be(operationKey); + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/PolicyContextAndKeySpecs.cs b/src/Polly.Specs/PolicyContextAndKeySpecs.cs index a414311d399..83a322e0b36 100644 --- a/src/Polly.Specs/PolicyContextAndKeySpecs.cs +++ b/src/Polly.Specs/PolicyContextAndKeySpecs.cs @@ -3,321 +3,322 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs; - -public class PolicyKeySpecs +namespace Polly.Specs { - #region Configuration - - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() + public class PolicyKeySpecs { - var policy = Policy.Handle().Retry().WithPolicyKey(Guid.NewGuid().ToString()); + #region Configuration - policy.Should().BeAssignableTo(); - } + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() + { + var policy = Policy.Handle().Retry().WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - ISyncPolicy policyAsInterface = Policy.Handle().Retry(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); + policy.Should().BeAssignableTo(); + } - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); - } - - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + ISyncPolicy policyAsInterface = Policy.Handle().Retry(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - var policy = Policy.Handle().Retry().WithPolicyKey(key); + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo(); + } - policy.PolicyKey.Should().Be(key); - } + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.Handle().Retry(); + var policy = Policy.Handle().Retry().WithPolicyKey(key); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + policy.PolicyKey.Should().Be(key); + } - configure.Should().NotThrow(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.Handle().Retry(); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.Handle().Retry(); + configure.Should().NotThrow(); - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.Handle().Retry(); + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.Handle().Retry(); - policy.PolicyKey.Should().StartWith("Retry"); - } + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.Handle().Retry(); - var policy2 = Policy.Handle().Retry(); + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.Handle().Retry(); - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + policy.PolicyKey.Should().StartWith("Retry"); + } - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.Handle().Retry(); + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.Handle().Retry(); + var policy2 = Policy.Handle().Retry(); - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.Handle().Retry(); + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.Handle().Retry(); + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.Handle().Retry(); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - #endregion + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - #region PolicyKey and execution Context tests + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void Should_pass_PolicyKey_to_execution_context() - { - var policyKey = Guid.NewGuid().ToString(); + #endregion - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); + #region PolicyKey and execution Context tests - retry.RaiseException(1); + [Fact] + public void Should_pass_PolicyKey_to_execution_context() + { + var policyKey = Guid.NewGuid().ToString(); - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); - [Fact] - public void Should_pass_OperationKey_to_execution_context() - { - var operationKey = "SomeKey"; + retry.RaiseException(1); - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().Retry(1, onRetry); + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - var firstExecution = true; - retry.Execute(_ => + [Fact] + public void Should_pass_OperationKey_to_execution_context() { - if (firstExecution) - { - firstExecution = false; - throw new Exception(); - } - }, new Context(operationKey)); - - operationKeySetOnContext.Should().Be(operationKey); - } - - [Fact] - public void Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() - { - var policyKey = Guid.NewGuid().ToString(); + var operationKey = "SomeKey"; - string policyKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().Retry(1, onRetry); - var firstExecution = true; - retry.Execute(() => - { - if (firstExecution) + var firstExecution = true; + retry.Execute(_ => { - firstExecution = false; - throw new Exception(); - } - return 0; - }); + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + }, new Context(operationKey)); + + operationKeySetOnContext.Should().Be(operationKey); + } + + [Fact] + public void Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() + { + var policyKey = Guid.NewGuid().ToString(); - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + string policyKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); - [Fact] - public void Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() - { - var operationKey = "SomeKey"; + var firstExecution = true; + retry.Execute(() => + { + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + return 0; + }); + + policyKeySetOnExecutionContext.Should().Be(policyKey); + } + + [Fact] + public void Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() + { + var operationKey = "SomeKey"; - string operationKeySetOnContext = null; - Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.Handle().Retry(1, onRetry); + string operationKeySetOnContext = null; + Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.Handle().Retry(1, onRetry); - var firstExecution = true; - retry.Execute(_ => - { - if (firstExecution) + var firstExecution = true; + retry.Execute(_ => { - firstExecution = false; - throw new Exception(); - } - return 0; - }, new Context(operationKey)); - - operationKeySetOnContext.Should().Be(operationKey); + if (firstExecution) + { + firstExecution = false; + throw new Exception(); + } + return 0; + }, new Context(operationKey)); + + operationKeySetOnContext.Should().Be(operationKey); + } + #endregion } - #endregion -} - -public class PolicyTResultKeySpecs -{ - #region Configuration - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key() + public class PolicyTResultKeySpecs { - var policy = Policy.HandleResult(0).Retry().WithPolicyKey(Guid.NewGuid().ToString()); + #region Configuration - policy.Should().BeAssignableTo>(); - } - - [Fact] - public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() - { - ISyncPolicy policyAsInterface = Policy.HandleResult(0).Retry(); - var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key() + { + var policy = Policy.HandleResult(0).Retry().WithPolicyKey(Guid.NewGuid().ToString()); - policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); - } + policy.Should().BeAssignableTo>(); + } - [Fact] - public void PolicyKey_property_should_be_the_fluently_configured_policy_key() - { - const string key = "SomePolicyKey"; + [Fact] + public void Should_be_able_fluently_to_configure_the_policy_key_via_interface() + { + ISyncPolicy policyAsInterface = Policy.HandleResult(0).Retry(); + var policyAsInterfaceAfterWithPolicyKey = policyAsInterface.WithPolicyKey(Guid.NewGuid().ToString()); - var policy = Policy.HandleResult(0).Retry().WithPolicyKey(key); + policyAsInterfaceAfterWithPolicyKey.Should().BeAssignableTo>(); + } - policy.PolicyKey.Should().Be(key); - } + [Fact] + public void PolicyKey_property_should_be_the_fluently_configured_policy_key() + { + const string key = "SomePolicyKey"; - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() - { - var policy = Policy.HandleResult(0).Retry(); + var policy = Policy.HandleResult(0).Retry().WithPolicyKey(key); - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + policy.PolicyKey.Should().Be(key); + } - configure.Should().NotThrow(); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_more_than_once() + { + var policy = Policy.HandleResult(0).Retry(); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - [Fact] - public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).Retry(); + configure.Should().NotThrow(); - policy.PolicyKey.Should().NotBeNullOrEmpty(); - } + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).Retry(); + [Fact] + public void PolicyKey_property_should_be_non_null_or_empty_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).Retry(); - policy.PolicyKey.Should().StartWith("Retry"); - } + policy.PolicyKey.Should().NotBeNullOrEmpty(); + } - [Fact] - public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() - { - var policy1 = Policy.HandleResult(0).Retry(); - var policy2 = Policy.HandleResult(0).Retry(); + [Fact] + public void PolicyKey_property_should_start_with_policy_type_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).Retry(); - policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); - } + policy.PolicyKey.Should().StartWith("Retry"); + } - [Fact] - public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() - { - var policy = Policy.HandleResult(0).Retry(); + [Fact] + public void PolicyKey_property_should_be_unique_for_different_instances_if_not_explicitly_configured() + { + var policy1 = Policy.HandleResult(0).Retry(); + var policy2 = Policy.HandleResult(0).Retry(); - var keyRetrievedFirst = policy.PolicyKey; - var keyRetrievedSecond = policy.PolicyKey; + policy1.PolicyKey.Should().NotBe(policy2.PolicyKey); + } - keyRetrievedSecond.Should().Be(keyRetrievedFirst); - } + [Fact] + public void PolicyKey_property_should_return_consistent_value_for_same_policy_instance_if_not_explicitly_configured() + { + var policy = Policy.HandleResult(0).Retry(); + var keyRetrievedFirst = policy.PolicyKey; + var keyRetrievedSecond = policy.PolicyKey; - [Fact] - public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() - { - var policy = Policy.HandleResult(0).Retry(); + keyRetrievedSecond.Should().Be(keyRetrievedFirst); + } - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); + [Fact] + public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retrieving_default_value() + { + var policy = Policy.HandleResult(0).Retry(); - configure.Should().Throw().And.ParamName.Should().Be("policyKey"); - } + var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; - #endregion + Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); - #region PolicyKey and execution Context tests + configure.Should().Throw().And.ParamName.Should().Be("policyKey"); + } - [Fact] - public void Should_pass_PolicyKey_to_execution_context() - { - var policyKey = Guid.NewGuid().ToString(); + #endregion - string policyKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(policyKey); + #region PolicyKey and execution Context tests - retry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + [Fact] + public void Should_pass_PolicyKey_to_execution_context() + { + var policyKey = Guid.NewGuid().ToString(); - policyKeySetOnExecutionContext.Should().Be(policyKey); - } + string policyKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(policyKey); - [Fact] - public void Should_pass_OperationKey_to_execution_context() - { - var operationKey = "SomeKey"; + retry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - string operationKeySetOnContext = null; - Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry); + policyKeySetOnExecutionContext.Should().Be(policyKey); + } - var firstExecution = true; - retry.Execute(_ => + [Fact] + public void Should_pass_OperationKey_to_execution_context() { - if (firstExecution) - { - firstExecution = false; - return ResultPrimitive.Fault; - } - return ResultPrimitive.Good; - }, new Context(operationKey)); + var operationKey = "SomeKey"; - operationKeySetOnContext.Should().Be(operationKey); - } + string operationKeySetOnContext = null; + Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry); - #endregion -} \ No newline at end of file + var firstExecution = true; + retry.Execute(_ => + { + if (firstExecution) + { + firstExecution = false; + return ResultPrimitive.Fault; + } + return ResultPrimitive.Good; + }, new Context(operationKey)); + + operationKeySetOnContext.Should().Be(operationKey); + } + + #endregion + } +} diff --git a/src/Polly.Specs/PolicySpecs.cs b/src/Polly.Specs/PolicySpecs.cs index 517108c8f42..df986ee1898 100644 --- a/src/Polly.Specs/PolicySpecs.cs +++ b/src/Polly.Specs/PolicySpecs.cs @@ -3,292 +3,293 @@ using FluentAssertions; using Xunit; -namespace Polly.Specs; - -public class PolicySpecs +namespace Polly.Specs { - #region Execute tests - - [Fact] - public void Executing_the_policy_action_should_execute_the_specified_action() + public class PolicySpecs { - var executed = false; + #region Execute tests - var policy = Policy - .Handle() - .Retry((_, _) => { }); + [Fact] + public void Executing_the_policy_action_should_execute_the_specified_action() + { + var executed = false; - policy.Execute(() => executed = true); + var policy = Policy + .Handle() + .Retry((_, _) => { }); - executed.Should() - .BeTrue(); - } + policy.Execute(() => executed = true); - [Fact] - public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() - { - var policy = Policy - .Handle() - .Retry((_, _) => { }); + executed.Should() + .BeTrue(); + } - var result = policy.Execute(() => 2); + [Fact] + public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() + { + var policy = Policy + .Handle() + .Retry((_, _) => { }); - result.Should() - .Be(2); - } + var result = policy.Execute(() => 2); - #endregion + result.Should() + .Be(2); + } - #region ExecuteAndCapture tests + #endregion - [Fact] - public void Executing_the_policy_action_successfully_should_return_success_result() - { - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => { }); + #region ExecuteAndCapture tests - result.Should().BeEquivalentTo(new + [Fact] + public void Executing_the_policy_action_successfully_should_return_success_result() { - Outcome = OutcomeType.Successful, - FinalException = (Exception) null, - ExceptionType = (ExceptionType?) null, - }); - } - - [Fact] - public void Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() - { - var handledException = new DivideByZeroException(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw handledException); - - result.Should().BeEquivalentTo(new + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => { }); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception) null, + ExceptionType = (ExceptionType?) null, + }); + } + + [Fact] + public void Executing_the_policy_action_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy - }); - } - - [Fact] - public void Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw unhandledException); - - result.Should().BeEquivalentTo(new + var handledException = new DivideByZeroException(); + + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw handledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy + }); + } + + [Fact] + public void Executing_the_policy_action_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled - }); - } - - [Fact] - public void Executing_the_policy_function_successfully_should_return_success_result() - { - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => Int32.MaxValue); - - result.Should().BeEquivalentTo(new + var unhandledException = new Exception(); + + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw unhandledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled + }); + } + + [Fact] + public void Executing_the_policy_function_successfully_should_return_success_result() { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(int), - Result = Int32.MaxValue - }); - } - - [Fact] - public void Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() - { - var handledException = new DivideByZeroException(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw handledException); - - result.Should().BeEquivalentTo(new + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => Int32.MaxValue); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(int), + Result = Int32.MaxValue + }); + } + + [Fact] + public void Executing_the_policy_function_and_failing_with_a_handled_exception_type_should_return_failure_result_indicating_that_exception_type_is_one_handled_by_this_policy() { - Outcome = OutcomeType.Failure, - FinalException = handledException, - ExceptionType = ExceptionType.HandledByThisPolicy, - FaultType = FaultType.ExceptionHandledByThisPolicy, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - [Fact] - public void Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() - { - var unhandledException = new Exception(); - - var result = Policy - .Handle() - .Retry((_, _) => { }) - .ExecuteAndCapture(() => throw unhandledException); - - result.Should().BeEquivalentTo(new + var handledException = new DivideByZeroException(); + + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw handledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = handledException, + ExceptionType = ExceptionType.HandledByThisPolicy, + FaultType = FaultType.ExceptionHandledByThisPolicy, + FinalHandledResult = default(int), + Result = default(int) + }); + } + + [Fact] + public void Executing_the_policy_function_and_failing_with_an_unhandled_exception_type_should_return_failure_result_indicating_that_exception_type_is_unhandled_by_this_policy() { - Outcome = OutcomeType.Failure, - FinalException = unhandledException, - ExceptionType = ExceptionType.Unhandled, - FaultType = FaultType.UnhandledException, - FinalHandledResult = default(int), - Result = default(int) - }); - } - - #endregion + var unhandledException = new Exception(); + + var result = Policy + .Handle() + .Retry((_, _) => { }) + .ExecuteAndCapture(() => throw unhandledException); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = unhandledException, + ExceptionType = ExceptionType.Unhandled, + FaultType = FaultType.UnhandledException, + FinalHandledResult = default(int), + Result = default(int) + }); + } + + #endregion + + #region Context tests + + [Fact] + public void Executing_the_policy_action_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - #region Context tests + policy.Invoking(p => p.Execute(_ => { }, (IDictionary)null)) + .Should().Throw(); + } - [Fact] - public void Executing_the_policy_action_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + [Fact] + public void Executing_the_policy_action_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - policy.Invoking(p => p.Execute(_ => { }, (IDictionary)null)) - .Should().Throw(); - } + policy.Invoking(p => p.Execute(_ => { }, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - [Fact] - public void Executing_the_policy_action_should_throw_when_context_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); - - policy.Invoking(p => p.Execute(_ => { }, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + policy.Invoking(p => p.Execute(_ => 2, (IDictionary)null)) + .Should().Throw(); + } - policy.Invoking(p => p.Execute(_ => 2, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + policy.Invoking(p => p.Execute(_ => 2, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Invoking(p => p.Execute(_ => 2, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public void Executing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + Policy policy = Policy.NoOp(); - Policy policy = Policy.NoOp(); + policy.Execute(context => { capturedContext = context; }, executionContext); - policy.Execute(context => { capturedContext = context; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (IDictionary)null)) + .Should().Throw(); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_action_should_throw_when_context_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => { }, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (IDictionary)null)) + .Should().Throw(); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .Handle() + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - Policy policy = Policy - .Handle() - .Retry((_, _, _) => { }); + policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => 2, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + Policy policy = Policy.NoOp(); - Policy policy = Policy.NoOp(); + policy.ExecuteAndCapture(context => { capturedContext = context; }, executionContext); - policy.ExecuteAndCapture(context => { capturedContext = context; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + Policy policy = Policy.NoOp(); - Policy policy = Policy.NoOp(); + policy.ExecuteAndCapture(_ => { }, executionContext) + .Context.Should().BeSameAs(executionContext); + } - policy.ExecuteAndCapture(_ => { }, executionContext) - .Context.Should().BeSameAs(executionContext); + #endregion } - - #endregion } \ No newline at end of file diff --git a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs index 1ab76fb217c..3c39d814a16 100644 --- a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs @@ -5,182 +5,183 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs; - -public class PolicyTResultAsyncSpecs +namespace Polly.Specs { - #region Execute tests - - [Fact] - public async Task Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() + public class PolicyTResultAsyncSpecs { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _) => { }); + #region Execute tests - var result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + [Fact] + public async Task Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _) => { }); - result.Should() - .Be(ResultPrimitive.Good); - } + var result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - #endregion + result.Should() + .Be(ResultPrimitive.Good); + } - #region ExecuteAndCapture tests + #endregion - [Fact] - public async Task Executing_the_policy_function_successfully_should_return_success_result() - { - var result = await Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Good)); + #region ExecuteAndCapture tests - result.Should().BeEquivalentTo(new + [Fact] + public async Task Executing_the_policy_function_successfully_should_return_success_result() { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = ResultPrimitive.Good, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } - - [Fact] - public async Task Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() - { - var handledResult = ResultPrimitive.Fault; - - var result = await Policy - .HandleResult(handledResult) - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(handledResult)); - - result.Should().BeEquivalentTo(new + var result = await Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Good)); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = ResultPrimitive.Good, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } + + [Fact] + public async Task Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() { - Outcome = OutcomeType.Failure, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = FaultType.ResultHandledByThisPolicy, - FinalHandledResult = handledResult, - Result = default(ResultPrimitive) - }); - } + var handledResult = ResultPrimitive.Fault; + + var result = await Policy + .HandleResult(handledResult) + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(handledResult)); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = FaultType.ResultHandledByThisPolicy, + FinalHandledResult = handledResult, + Result = default(ResultPrimitive) + }); + } + + [Fact] + public async Task Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() + { + var handledResult = ResultPrimitive.Fault; + var unhandledResult = ResultPrimitive.Good; - [Fact] - public async Task Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() - { - var handledResult = ResultPrimitive.Fault; - var unhandledResult = ResultPrimitive.Good; + var result = await Policy + .HandleResult(handledResult) + .RetryAsync((_, _) => { }) + .ExecuteAndCaptureAsync(() => Task.FromResult(unhandledResult)); - var result = await Policy - .HandleResult(handledResult) - .RetryAsync((_, _) => { }) - .ExecuteAndCaptureAsync(() => Task.FromResult(unhandledResult)); + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = unhandledResult, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = unhandledResult, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } + #endregion - #endregion + #region Context tests - #region Context tests + [Fact] + public void Executing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (IDictionary)null)) + .Should().Throw(); + } - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Awaiting(p => p.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + await policy.ExecuteAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); - await policy.ExecuteAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, _) => { }); + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); - await policy.ExecuteAndCaptureAsync(context => { capturedContext = context; return Task.FromResult(ResultPrimitive.Good); }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); - [Fact] - public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + var policy = Policy.NoOpAsync(); - var policy = Policy.NoOpAsync(); + (await policy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), executionContext)) + .Context.Should().BeSameAs(executionContext); + } - (await policy.ExecuteAndCaptureAsync(_ => Task.FromResult(ResultPrimitive.Good), executionContext)) - .Context.Should().BeSameAs(executionContext); + #endregion } - - #endregion } \ No newline at end of file diff --git a/src/Polly.Specs/PolicyTResultSpecs.cs b/src/Polly.Specs/PolicyTResultSpecs.cs index b78a1f65023..ca28b3745a8 100644 --- a/src/Polly.Specs/PolicyTResultSpecs.cs +++ b/src/Polly.Specs/PolicyTResultSpecs.cs @@ -4,180 +4,181 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs; - -public class PolicyTResultSpecs +namespace Polly.Specs { - #region Execute tests - - [Fact] - public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() + public class PolicyTResultSpecs { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _) => { }); + #region Execute tests - var result = policy.Execute(() => ResultPrimitive.Good); + [Fact] + public void Executing_the_policy_function_should_execute_the_specified_function_and_return_the_result() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _) => { }); - result.Should() - .Be(ResultPrimitive.Good); - } + var result = policy.Execute(() => ResultPrimitive.Good); - #endregion + result.Should() + .Be(ResultPrimitive.Good); + } - #region ExecuteAndCapture tests + #endregion - [Fact] - public void Executing_the_policy_function_successfully_should_return_success_result() - { - var result = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _) => { }) - .ExecuteAndCapture(() => ResultPrimitive.Good); + #region ExecuteAndCapture tests - result.Should().BeEquivalentTo(new + [Fact] + public void Executing_the_policy_function_successfully_should_return_success_result() { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = ResultPrimitive.Good, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } - - [Fact] - public void Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() - { - var handledResult = ResultPrimitive.Fault; - - var result = Policy - .HandleResult(handledResult) - .Retry((_, _) => { }) - .ExecuteAndCapture(() => handledResult); - - result.Should().BeEquivalentTo(new + var result = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _) => { }) + .ExecuteAndCapture(() => ResultPrimitive.Good); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = ResultPrimitive.Good, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } + + [Fact] + public void Executing_the_policy_function_and_failing_with_a_handled_result_should_return_failure_result_indicating_that_result_is_one_handled_by_this_policy() { - Outcome = OutcomeType.Failure, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = FaultType.ResultHandledByThisPolicy, - FinalHandledResult = handledResult, - Result = default(ResultPrimitive) - }); - } - - [Fact] - public void Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() - { - var handledResult = ResultPrimitive.Fault; - var unhandledResult = ResultPrimitive.Good; - - var result = Policy - .HandleResult(handledResult) - .Retry((_, _) => { }) - .ExecuteAndCapture(() => unhandledResult); - - result.Should().BeEquivalentTo(new + var handledResult = ResultPrimitive.Fault; + + var result = Policy + .HandleResult(handledResult) + .Retry((_, _) => { }) + .ExecuteAndCapture(() => handledResult); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Failure, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = FaultType.ResultHandledByThisPolicy, + FinalHandledResult = handledResult, + Result = default(ResultPrimitive) + }); + } + + [Fact] + public void Executing_the_policy_function_and_returning_an_unhandled_result_should_return_result_not_indicating_any_failure() { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - Result = unhandledResult, - FinalHandledResult = default(ResultPrimitive), - FaultType = (FaultType?)null - }); - } - - #endregion - - #region Context tests + var handledResult = ResultPrimitive.Fault; + var unhandledResult = ResultPrimitive.Good; + + var result = Policy + .HandleResult(handledResult) + .Retry((_, _) => { }) + .ExecuteAndCapture(() => unhandledResult); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + Result = unhandledResult, + FinalHandledResult = default(ResultPrimitive), + FaultType = (FaultType?)null + }); + } + + #endregion + + #region Context tests + + [Fact] + public void Executing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (IDictionary)null)) + .Should().Throw(); + } - policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Executing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - [Fact] - public void Executing_the_policy_function_should_throw_when_context_is_null() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Invoking(p => p.Execute(_ => ResultPrimitive.Good, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Executing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public void Executing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + Policy policy = Policy.NoOp(); - Policy policy = Policy.NoOp(); + policy.Execute(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); - policy.Execute(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_data_is_null() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (IDictionary)null)) + .Should().Throw(); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (IDictionary)null)) - .Should().Throw(); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, _) => { }); - [Fact] - public void Execute_and_capturing_the_policy_function_should_throw_when_context_is_null() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, _) => { }); + policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (Context)null)) + .Should().Throw().And + .ParamName.Should().Be("context"); + } - policy.Invoking(p => p.ExecuteAndCapture(_ => ResultPrimitive.Good, (Context)null)) - .Should().Throw().And - .ParamName.Should().Be("context"); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); + Context capturedContext = null; - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); - Context capturedContext = null; + Policy policy = Policy.NoOp(); - Policy policy = Policy.NoOp(); + policy.ExecuteAndCapture(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); - policy.ExecuteAndCapture(context => { capturedContext = context; return ResultPrimitive.Good; }, executionContext); + capturedContext.Should().BeSameAs(executionContext); + } - capturedContext.Should().BeSameAs(executionContext); - } + [Fact] + public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() + { + var operationKey = "SomeKey"; + var executionContext = new Context(operationKey); - [Fact] - public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() - { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + Policy policy = Policy.NoOp(); - Policy policy = Policy.NoOp(); + policy.ExecuteAndCapture(_ => ResultPrimitive.Good, executionContext) + .Context.Should().BeSameAs(executionContext); + } - policy.ExecuteAndCapture(_ => ResultPrimitive.Good, executionContext) - .Context.Should().BeSameAs(executionContext); + #endregion } - - #endregion } \ No newline at end of file diff --git a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs index f14a2821ccf..24b70fa2e50 100644 --- a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs +++ b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs @@ -6,43 +6,44 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit; - -[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] -public class AsyncRateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable +namespace Polly.Specs.RateLimit { - public void Dispose() + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public class AsyncRateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + } - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) - { - if (policy is AsyncRateLimitPolicy typedPolicy) + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { - try + if (policy is AsyncRateLimitPolicy typedPolicy) { - typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); - return (true, TimeSpan.Zero); + try + { + typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); + return (true, TimeSpan.Zero); + } + catch (RateLimitRejectedException e) + { + return (false, e.RetryAfter); + } } - catch (RateLimitRejectedException e) + else { - return (false, e.RetryAfter); + throw new InvalidOperationException("Unexpected policy type in test construction."); } } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); - } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs index 6d362e9940e..19073d3f3ed 100644 --- a/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs +++ b/src/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs @@ -6,61 +6,62 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit; - -[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] -public class AsyncRateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable +namespace Polly.Specs.RateLimit { - public void Dispose() + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public class AsyncRateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, - Func retryAfterFactory) - { - return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, + Func retryAfterFactory) + { + return Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + } - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) - { - if (policy is AsyncRateLimitPolicy typedPolicy) + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { - try + if (policy is AsyncRateLimitPolicy typedPolicy) { - typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); - return (true, TimeSpan.Zero); + try + { + typedPolicy.ExecuteAsync(() => Task.FromResult(new ResultClassWithRetryAfter(ResultPrimitive.Good))).GetAwaiter().GetResult(); + return (true, TimeSpan.Zero); + } + catch (RateLimitRejectedException e) + { + return (false, e.RetryAfter); + } } - catch (RateLimitRejectedException e) + else { - return (false, e.RetryAfter); + throw new InvalidOperationException("Unexpected policy type in test construction."); } } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); - } - } - protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) - { - if (policy is AsyncRateLimitPolicy typedPolicy) + protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) { - return typedPolicy.ExecuteAsync(ctx => Task.FromResult(resultIfExecutionPermitted), context).GetAwaiter().GetResult(); - } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); + if (policy is AsyncRateLimitPolicy typedPolicy) + { + return typedPolicy.ExecuteAsync(ctx => Task.FromResult(resultIfExecutionPermitted), context).GetAwaiter().GetResult(); + } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); + } } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs b/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs index 295a1574069..31376594f63 100644 --- a/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs +++ b/src/Polly.Specs/RateLimit/LockFreeTokenBucketRateLimiterTests.cs @@ -1,10 +1,11 @@ using System; using Polly.RateLimit; -namespace Polly.Specs.RateLimit; - -public class LockFreeTokenBucketRateLimiterTests : TokenBucketRateLimiterTestsBase +namespace Polly.Specs.RateLimit { - internal override IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity) - => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); -} \ No newline at end of file + public class LockFreeTokenBucketRateLimiterTests : TokenBucketRateLimiterTestsBase + { + internal override IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity) + => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); + } +} diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs b/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs index 0e32c4e2653..dec7aa35f0d 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicySpecs.cs @@ -5,43 +5,44 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit; - -[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] -public class RateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable +namespace Polly.Specs.RateLimit { - public void Dispose() + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public class RateLimitPolicySpecs : RateLimitPolicySpecsBase, IDisposable { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + } - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) - { - if (policy is RateLimitPolicy typedPolicy) + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { - try + if (policy is RateLimitPolicy typedPolicy) { - typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); - return (true, TimeSpan.Zero); + try + { + typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); + return (true, TimeSpan.Zero); + } + catch (RateLimitRejectedException e) + { + return (false, e.RetryAfter); + } } - catch (RateLimitRejectedException e) + else { - return (false, e.RetryAfter); + throw new InvalidOperationException("Unexpected policy type in test construction."); } } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); - } } } \ No newline at end of file diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs index dbeec16b47a..24f3f62fa0e 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs @@ -6,307 +6,308 @@ using Polly.RateLimit; using Xunit; -namespace Polly.Specs.RateLimit; - -public abstract class RateLimitPolicySpecsBase : RateLimitSpecsBase +namespace Polly.Specs.RateLimit { - protected abstract IRateLimitPolicy GetPolicyViaSyntax( - int numberOfExecutions, - TimeSpan perTimeSpan); - - protected abstract IRateLimitPolicy GetPolicyViaSyntax( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst); - - protected abstract (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy); - - protected void ShouldPermitAnExecution(IRateLimitPolicy policy) + public abstract class RateLimitPolicySpecsBase : RateLimitSpecsBase { - (var permitExecution, var retryAfter) = TryExecuteThroughPolicy(policy); + protected abstract IRateLimitPolicy GetPolicyViaSyntax( + int numberOfExecutions, + TimeSpan perTimeSpan); - permitExecution.Should().BeTrue(); - retryAfter.Should().Be(TimeSpan.Zero); - } + protected abstract IRateLimitPolicy GetPolicyViaSyntax( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst); - protected void ShouldPermitNExecutions(IRateLimitPolicy policy, long numberOfExecutions) - { - for (var execution = 0; execution < numberOfExecutions; execution++) + protected abstract (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy); + + protected void ShouldPermitAnExecution(IRateLimitPolicy policy) { - ShouldPermitAnExecution(policy); - } - } + (var permitExecution, var retryAfter) = TryExecuteThroughPolicy(policy); - protected void ShouldNotPermitAnExecution(IRateLimitPolicy policy, TimeSpan? retryAfter = null) - { - (bool permitExecution, TimeSpan retryAfter) canExecute = TryExecuteThroughPolicy(policy); + permitExecution.Should().BeTrue(); + retryAfter.Should().Be(TimeSpan.Zero); + } - canExecute.permitExecution.Should().BeFalse(); - if (retryAfter == null) + protected void ShouldPermitNExecutions(IRateLimitPolicy policy, long numberOfExecutions) { - canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); + for (var execution = 0; execution < numberOfExecutions; execution++) + { + ShouldPermitAnExecution(policy); + } } - else + + protected void ShouldNotPermitAnExecution(IRateLimitPolicy policy, TimeSpan? retryAfter = null) { - canExecute.retryAfter.Should().Be(retryAfter.Value); + (bool permitExecution, TimeSpan retryAfter) canExecute = TryExecuteThroughPolicy(policy); + + canExecute.permitExecution.Should().BeFalse(); + if (retryAfter == null) + { + canExecute.retryAfter.Should().BeGreaterThan(TimeSpan.Zero); + } + else + { + canExecute.retryAfter.Should().Be(retryAfter.Value); + } } - } - [Fact] - public void Syntax_should_throw_for_perTimeSpan_zero() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.Zero); + [Fact] + public void Syntax_should_throw_for_perTimeSpan_zero() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.Zero); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + } - [Fact] - public void Syntax_should_throw_for_perTimeSpan_infinite() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, System.Threading.Timeout.InfiniteTimeSpan); + [Fact] + public void Syntax_should_throw_for_perTimeSpan_infinite() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, System.Threading.Timeout.InfiniteTimeSpan); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + } - [Fact] - public void Syntax_should_throw_for_perTimeSpan_too_small() - { - Action invalidSyntax = () => GetPolicyViaSyntax(int.MaxValue, TimeSpan.FromSeconds(1)); + [Fact] + public void Syntax_should_throw_for_perTimeSpan_too_small() + { + Action invalidSyntax = () => GetPolicyViaSyntax(int.MaxValue, TimeSpan.FromSeconds(1)); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); - invalidSyntax.Should().Throw().And.Message.Should().StartWith("The number of executions per timespan must be positive."); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + invalidSyntax.Should().Throw().And.Message.Should().StartWith("The number of executions per timespan must be positive."); + } - [Fact] - public void Syntax_should_throw_for_numberOfExecutions_negative() - { - Action invalidSyntax = () => GetPolicyViaSyntax(-1, TimeSpan.FromSeconds(1)); + [Fact] + public void Syntax_should_throw_for_numberOfExecutions_negative() + { + Action invalidSyntax = () => GetPolicyViaSyntax(-1, TimeSpan.FromSeconds(1)); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); + } - [Fact] - public void Syntax_should_throw_for_numberOfExecutions_zero() - { - Action invalidSyntax = () => GetPolicyViaSyntax(0, TimeSpan.FromSeconds(1)); + [Fact] + public void Syntax_should_throw_for_numberOfExecutions_zero() + { + Action invalidSyntax = () => GetPolicyViaSyntax(0, TimeSpan.FromSeconds(1)); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("numberOfExecutions"); + } - [Fact] - public void Syntax_should_throw_for_perTimeSpan_negative() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromTicks(-1)); + [Fact] + public void Syntax_should_throw_for_perTimeSpan_negative() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromTicks(-1)); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("perTimeSpan"); + } - [Fact] - public void Syntax_should_throw_for_maxBurst_negative() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), -1); + [Fact] + public void Syntax_should_throw_for_maxBurst_negative() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), -1); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); + } - [Fact] - public void Syntax_should_throw_for_maxBurst_zero() - { - Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), 0); + [Fact] + public void Syntax_should_throw_for_maxBurst_zero() + { + Action invalidSyntax = () => GetPolicyViaSyntax(1, TimeSpan.FromSeconds(1), 0); - invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); - } + invalidSyntax.Should().Throw().And.ParamName.Should().Be("maxBurst"); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) - { - FixClock(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) + { + FixClock(); - // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer); + // Arrange + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer); - // Assert - first execution after initialising should always be permitted. - ShouldPermitAnExecution(rateLimiter); + // Assert - first execution after initialising should always be permitted. + ShouldPermitAnExecution(rateLimiter); - // Arrange - // (do nothing - time not advanced) + // Arrange + // (do nothing - time not advanced) - // Assert - should be blocked - time not advanced. - ShouldNotPermitAnExecution(rateLimiter, onePer); - } + // Assert - should be blocked - time not advanced. + ShouldNotPermitAnExecution(rateLimiter, onePer); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(50)] - public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(50)] + public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) + { + FixClock(); - // Arrange. - var onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange. + var onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Act - should be able to successfully take bucketCapacity items. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); + // Act - should be able to successfully take bucketCapacity items. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); - // Assert - should not be able to take any items (given time not advanced). - ShouldNotPermitAnExecution(rateLimiter, onePer); - } + // Assert - should not be able to take any items (given time not advanced). + ShouldNotPermitAnExecution(rateLimiter, onePer); + } - [Theory] - [InlineData(1, 1)] - [InlineData(2, 1)] - [InlineData(5, 1)] - [InlineData(1, 10)] - [InlineData(2, 10)] - [InlineData(5, 10)] - public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(1, 1)] + [InlineData(2, 1)] + [InlineData(5, 1)] + [InlineData(1, 10)] + [InlineData(2, 10)] + [InlineData(5, 10)] + public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) + { + FixClock(); - // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval - var experimentRepeats = bucketCapacity * 3; - var shortfallFromInterval = TimeSpan.FromTicks(1); - var notQuiteInterval = onePer - shortfallFromInterval; - for (var i = 0; i < experimentRepeats; i++) - { - // Arrange - Advance clock not quite to the interval - AdvanceClock(notQuiteInterval.Ticks); + // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval + var experimentRepeats = bucketCapacity * 3; + var shortfallFromInterval = TimeSpan.FromTicks(1); + var notQuiteInterval = onePer - shortfallFromInterval; + for (var i = 0; i < experimentRepeats; i++) + { + // Arrange - Advance clock not quite to the interval + AdvanceClock(notQuiteInterval.Ticks); - // Assert - should not quite be able to issue another token - ShouldNotPermitAnExecution(rateLimiter, shortfallFromInterval); + // Assert - should not quite be able to issue another token + ShouldNotPermitAnExecution(rateLimiter, shortfallFromInterval); - // Arrange - Advance clock to the interval - AdvanceClock(shortfallFromInterval.Ticks); + // Arrange - Advance clock to the interval + AdvanceClock(shortfallFromInterval.Ticks); - // Act - ShouldPermitAnExecution(rateLimiter); + // Act + ShouldPermitAnExecution(rateLimiter); - // Assert - but cannot get another token straight away - ShouldNotPermitAnExecution(rateLimiter); + // Assert - but cannot get another token straight away + ShouldNotPermitAnExecution(rateLimiter); + } } - } - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) + { + FixClock(); - // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange + var onePerSeconds = 1; + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - // Arrange - advance exactly enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity); + // Arrange - advance exactly enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity); - // Assert - expect full bucket capacity but no more - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); - } + // Assert - expect full bucket capacity but no more + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); + } - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) - { - (bucketCapacity % 2).Should().Be(0); + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) + { + (bucketCapacity % 2).Should().Be(0); - FixClock(); + FixClock(); - // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange + var onePerSeconds = 1; + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); - // Assert - expect full bucket capacity but no more - ShouldPermitNExecutions(rateLimiter, bucketCapacity / 2); - ShouldNotPermitAnExecution(rateLimiter); - } + // Assert - expect full bucket capacity but no more + ShouldPermitNExecutions(rateLimiter, bucketCapacity / 2); + ShouldNotPermitAnExecution(rateLimiter); + } - [Theory] - [InlineData(100, 2)] - [InlineData(100, 5)] - public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) - { - multipleRefillTimePassed.Should().BeGreaterThan(1); + [Theory] + [InlineData(100, 2)] + [InlineData(100, 5)] + public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) + { + multipleRefillTimePassed.Should().BeGreaterThan(1); - FixClock(); + FixClock(); - // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); + // Arrange + var onePerSeconds = 1; + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); + // Arrange - spend the initial bucket capacity. + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); - // Assert - expect full bucket capacity but no more - ShouldPermitNExecutions(rateLimiter, bucketCapacity); - ShouldNotPermitAnExecution(rateLimiter); - } + // Assert - expect full bucket capacity but no more + ShouldPermitNExecutions(rateLimiter, bucketCapacity); + ShouldNotPermitAnExecution(rateLimiter); + } - [Theory] - [InlineData(2)] - [InlineData(5)] - [InlineData(100)] - public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) - { - FixClock(); + [Theory] + [InlineData(2)] + [InlineData(5)] + [InlineData(100)] + public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) + { + FixClock(); - // Arrange - var onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetPolicyViaSyntax(1, onePer); + // Arrange + var onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetPolicyViaSyntax(1, onePer); - // Arrange - parallel tasks all waiting on a manual reset event. - ManualResetEventSlim gate = new(); - Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; - for (var i = 0; i < parallelContention; i++) - { - tasks[i] = Task.Run(() => + // Arrange - parallel tasks all waiting on a manual reset event. + ManualResetEventSlim gate = new(); + Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; + for (var i = 0; i < parallelContention; i++) { - gate.Wait(); - return TryExecuteThroughPolicy(rateLimiter); - }); + tasks[i] = Task.Run(() => + { + gate.Wait(); + return TryExecuteThroughPolicy(rateLimiter); + }); + } + + // Act - release gate. + gate.Set(); + Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); + + // Assert - one should have permitted execution, n-1 not. + var results = tasks.Select(t => t.Result).ToList(); + results.Count(r => r.permitExecution).Should().Be(1); + results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); } - - // Act - release gate. - gate.Set(); - Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); - - // Assert - one should have permitted execution, n-1 not. - var results = tasks.Select(t => t.Result).ToList(); - results.Count(r => r.permitExecution).Should().Be(1); - results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs index 7b54699707b..e67df0c2fca 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecs.cs @@ -5,61 +5,62 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit; - -[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] -public class RateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable +namespace Polly.Specs.RateLimit { - public void Dispose() + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public class RateLimitPolicyTResultSpecs : RateLimitPolicyTResultSpecsBase, IDisposable { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst); + } - protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, - Func retryAfterFactory) - { - return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); - } + protected override IRateLimitPolicy GetPolicyViaSyntax(int numberOfExecutions, TimeSpan perTimeSpan, int maxBurst, + Func retryAfterFactory) + { + return Policy.RateLimit(numberOfExecutions, perTimeSpan, maxBurst, retryAfterFactory); + } - protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) - { - if (policy is RateLimitPolicy typedPolicy) + protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy policy) { - try + if (policy is RateLimitPolicy typedPolicy) { - typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); - return (true, TimeSpan.Zero); + try + { + typedPolicy.Execute(() => new ResultClassWithRetryAfter(ResultPrimitive.Good)); + return (true, TimeSpan.Zero); + } + catch (RateLimitRejectedException e) + { + return (false, e.RetryAfter); + } } - catch (RateLimitRejectedException e) + else { - return (false, e.RetryAfter); + throw new InvalidOperationException("Unexpected policy type in test construction."); } } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); - } - } - protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) - { - if (policy is RateLimitPolicy typedPolicy) + protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted) { - return typedPolicy.Execute(ctx => resultIfExecutionPermitted, context); - } - else - { - throw new InvalidOperationException("Unexpected policy type in test construction."); + if (policy is RateLimitPolicy typedPolicy) + { + return typedPolicy.Execute(ctx => resultIfExecutionPermitted, context); + } + else + { + throw new InvalidOperationException("Unexpected policy type in test construction."); + } } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs index 67e0f650b19..4b00d0a7669 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs @@ -5,52 +5,53 @@ using Polly.Specs.Helpers.RateLimit; using Xunit; -namespace Polly.Specs.RateLimit; - -public abstract class RateLimitPolicyTResultSpecsBase : RateLimitPolicySpecsBase +namespace Polly.Specs.RateLimit { - protected abstract IRateLimitPolicy GetPolicyViaSyntax( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst, - Func retryAfterFactory); - - protected abstract TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted); - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - public void Ratelimiter_specifies_correct_wait_until_next_execution_by_custom_factory_passing_correct_context(int onePerSeconds) + public abstract class RateLimitPolicyTResultSpecsBase : RateLimitPolicySpecsBase { - FixClock(); - - // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); - Context contextPassedToRetryAfter = null; - Func retryAfterFactory = (t, ctx) => + protected abstract IRateLimitPolicy GetPolicyViaSyntax( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst, + Func retryAfterFactory); + + protected abstract TResult TryExecuteThroughPolicy(IRateLimitPolicy policy, Context context, TResult resultIfExecutionPermitted); + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public void Ratelimiter_specifies_correct_wait_until_next_execution_by_custom_factory_passing_correct_context(int onePerSeconds) { - contextPassedToRetryAfter = ctx; - return new ResultClassWithRetryAfter(t); - }; - var rateLimiter = GetPolicyViaSyntax(1, onePer, 1, retryAfterFactory); - - // Arrange - drain first permitted execution after initialising. - ShouldPermitAnExecution(rateLimiter); - - // Arrange - // (do nothing - time not advanced) - - // Act - try another execution. - var contextToPassIn = new Context(); - var resultExpectedBlocked = TryExecuteThroughPolicy(rateLimiter, contextToPassIn, new ResultClassWithRetryAfter(ResultPrimitive.Good)); - - // Assert - should be blocked - time not advanced. - resultExpectedBlocked.ResultCode.Should().NotBe(ResultPrimitive.Good); - // Result should be expressed per the retryAfterFactory. - resultExpectedBlocked.RetryAfter.Should().Be(onePer); - // Context should have been passed to the retryAfterFactory. - contextPassedToRetryAfter.Should().NotBeNull(); - contextPassedToRetryAfter.Should().BeSameAs(contextToPassIn); + FixClock(); + + // Arrange + var onePer = TimeSpan.FromSeconds(onePerSeconds); + Context contextPassedToRetryAfter = null; + Func retryAfterFactory = (t, ctx) => + { + contextPassedToRetryAfter = ctx; + return new ResultClassWithRetryAfter(t); + }; + var rateLimiter = GetPolicyViaSyntax(1, onePer, 1, retryAfterFactory); + + // Arrange - drain first permitted execution after initialising. + ShouldPermitAnExecution(rateLimiter); + + // Arrange + // (do nothing - time not advanced) + + // Act - try another execution. + var contextToPassIn = new Context(); + var resultExpectedBlocked = TryExecuteThroughPolicy(rateLimiter, contextToPassIn, new ResultClassWithRetryAfter(ResultPrimitive.Good)); + + // Assert - should be blocked - time not advanced. + resultExpectedBlocked.ResultCode.Should().NotBe(ResultPrimitive.Good); + // Result should be expressed per the retryAfterFactory. + resultExpectedBlocked.RetryAfter.Should().Be(onePer); + // Context should have been passed to the retryAfterFactory. + contextPassedToRetryAfter.Should().NotBeNull(); + contextPassedToRetryAfter.Should().BeSameAs(contextToPassIn); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs index 1a447d98a13..9984e3ccab9 100644 --- a/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs @@ -5,49 +5,50 @@ using Polly.Utilities; using Xunit.Sdk; -namespace Polly.Specs.RateLimit; - -public abstract class RateLimitSpecsBase +namespace Polly.Specs.RateLimit { - /// - /// Asserts that the actionContainingAssertions will succeed without or , within the given timespan. Checks are made each time a status-change pulse is received from the s executing through the bulkhead. - /// - /// The allowable timespan. - /// The action containing fluent assertions, which must succeed within the timespan. - protected void Within(TimeSpan timeSpan, Action actionContainingAssertions) + public abstract class RateLimitSpecsBase { - var retryInterval = TimeSpan.FromSeconds(0.2); - - var watch = Stopwatch.StartNew(); - while (true) + /// + /// Asserts that the actionContainingAssertions will succeed without or , within the given timespan. Checks are made each time a status-change pulse is received from the s executing through the bulkhead. + /// + /// The allowable timespan. + /// The action containing fluent assertions, which must succeed within the timespan. + protected void Within(TimeSpan timeSpan, Action actionContainingAssertions) { - try + var retryInterval = TimeSpan.FromSeconds(0.2); + + var watch = Stopwatch.StartNew(); + while (true) { - actionContainingAssertions.Invoke(); - break; + try + { + actionContainingAssertions.Invoke(); + break; + } + catch (Exception e) + { + if (!(e is AssertionFailedException || e is XunitException)) { throw; } + + if (watch.Elapsed > timeSpan) { throw; } + + Thread.Sleep(retryInterval); + } } - catch (Exception e) - { - if (!(e is AssertionFailedException || e is XunitException)) { throw; } - - if (watch.Elapsed > timeSpan) { throw; } + } - Thread.Sleep(retryInterval); - } + protected static void FixClock() + { + var now = DateTimeOffset.UtcNow; + SystemClock.DateTimeOffsetUtcNow = () => now; } - } - protected static void FixClock() - { - var now = DateTimeOffset.UtcNow; - SystemClock.DateTimeOffsetUtcNow = () => now; - } + protected static void AdvanceClock(TimeSpan advance) + { + var now = SystemClock.DateTimeOffsetUtcNow(); + SystemClock.DateTimeOffsetUtcNow = () => now + advance; + } - protected static void AdvanceClock(TimeSpan advance) - { - var now = SystemClock.DateTimeOffsetUtcNow(); - SystemClock.DateTimeOffsetUtcNow = () => now + advance; + protected static void AdvanceClock(long advanceTicks) => AdvanceClock(TimeSpan.FromTicks(advanceTicks)); } - - protected static void AdvanceClock(long advanceTicks) => AdvanceClock(TimeSpan.FromTicks(advanceTicks)); -} \ No newline at end of file +} diff --git a/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs b/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs index 58b017ebb5d..222ad77a3a9 100644 --- a/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs +++ b/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs @@ -8,208 +8,209 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.RateLimit; - -[Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] -public abstract class TokenBucketRateLimiterTestsBase : RateLimitSpecsBase, IDisposable +namespace Polly.Specs.RateLimit { - internal abstract IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity); - - public void Dispose() + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public abstract class TokenBucketRateLimiterTestsBase : RateLimitSpecsBase, IDisposable { - SystemClock.Reset(); - } + internal abstract IRateLimiter GetRateLimiter(TimeSpan onePer, long bucketCapacity); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) - { - FixClock(); + public void Dispose() + { + SystemClock.Reset(); + } - // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, 1); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifies_correct_wait_until_next_execution(int onePerSeconds) + { + FixClock(); - // Assert - first execution after initialising should always be permitted. - rateLimiter.ShouldPermitAnExecution(); + // Arrange + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, 1); - // Arrange - // (do nothing - time not advanced) + // Assert - first execution after initialising should always be permitted. + rateLimiter.ShouldPermitAnExecution(); - // Assert - should be blocked - time not advanced. - rateLimiter.ShouldNotPermitAnExecution(onePer); - } + // Arrange + // (do nothing - time not advanced) - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(50)] - public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) - { - FixClock(); + // Assert - should be blocked - time not advanced. + rateLimiter.ShouldNotPermitAnExecution(onePer); + } - // Arrange. - var onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(50)] + public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_executions_up_to_bucket_capacity(int bucketCapacity) + { + FixClock(); - // Act - should be able to successfully take bucketCapacity items. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); + // Arrange. + var onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Assert - should not be able to take any items (given time not advanced). - rateLimiter.ShouldNotPermitAnExecution(onePer); - } + // Act - should be able to successfully take bucketCapacity items. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); - [Theory] - [InlineData(1, 1)] - [InlineData(2, 1)] - [InlineData(5, 1)] - [InlineData(1, 10)] - [InlineData(2, 10)] - [InlineData(5, 10)] - public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) - { - FixClock(); + // Assert - should not be able to take any items (given time not advanced). + rateLimiter.ShouldNotPermitAnExecution(onePer); + } - // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + [Theory] + [InlineData(1, 1)] + [InlineData(2, 1)] + [InlineData(5, 1)] + [InlineData(1, 10)] + [InlineData(2, 10)] + [InlineData(5, 10)] + public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_interval(int onePerSeconds, int bucketCapacity) + { + FixClock(); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval - var experimentRepeats = bucketCapacity * 3; - var shortfallFromInterval = TimeSpan.FromTicks(1); - var notQuiteInterval = onePer - shortfallFromInterval; - for (var i = 0; i < experimentRepeats; i++) - { - // Arrange - Advance clock not quite to the interval - AdvanceClock(notQuiteInterval.Ticks); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Assert - should not quite be able to issue another token - rateLimiter.ShouldNotPermitAnExecution(shortfallFromInterval); + // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval + var experimentRepeats = bucketCapacity * 3; + var shortfallFromInterval = TimeSpan.FromTicks(1); + var notQuiteInterval = onePer - shortfallFromInterval; + for (var i = 0; i < experimentRepeats; i++) + { + // Arrange - Advance clock not quite to the interval + AdvanceClock(notQuiteInterval.Ticks); - // Arrange - Advance clock to the interval - AdvanceClock(shortfallFromInterval.Ticks); + // Assert - should not quite be able to issue another token + rateLimiter.ShouldNotPermitAnExecution(shortfallFromInterval); - // Act - rateLimiter.ShouldPermitAnExecution(); + // Arrange - Advance clock to the interval + AdvanceClock(shortfallFromInterval.Ticks); - // Assert - but cannot get another token straight away - rateLimiter.ShouldNotPermitAnExecution(); + // Act + rateLimiter.ShouldPermitAnExecution(); + + // Assert - but cannot get another token straight away + rateLimiter.ShouldNotPermitAnExecution(); + } } - } - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) - { - FixClock(); + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_after_exact_elapsed_time(int bucketCapacity) + { + FixClock(); - // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange + var onePerSeconds = 1; + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Arrange - advance exactly enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity); + // Arrange - advance exactly enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity); - // Assert - expect full bucket capacity but no more - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); - } + // Assert - expect full bucket capacity but no more + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); + } - [Theory] - [InlineData(10)] - [InlineData(100)] - public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) - { - (bucketCapacity % 2).Should().Be(0); + [Theory] + [InlineData(10)] + [InlineData(100)] + public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burst_after_half_required_refill_time_elapsed(int bucketCapacity) + { + (bucketCapacity % 2).Should().Be(0); - FixClock(); + FixClock(); - // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange + var onePerSeconds = 1; + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * (bucketCapacity / 2)); - // Assert - expect full bucket capacity but no more - rateLimiter.ShouldPermitNExecutions(bucketCapacity / 2); - rateLimiter.ShouldNotPermitAnExecution(); - } + // Assert - expect full bucket capacity but no more + rateLimiter.ShouldPermitNExecutions(bucketCapacity / 2); + rateLimiter.ShouldNotPermitAnExecution(); + } - [Theory] - [InlineData(100, 2)] - [InlineData(100, 5)] - public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) - { - multipleRefillTimePassed.Should().BeGreaterThan(1); + [Theory] + [InlineData(100, 2)] + [InlineData(100, 5)] + public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burst_even_if_multiple_required_refill_time_elapsed(int bucketCapacity, int multipleRefillTimePassed) + { + multipleRefillTimePassed.Should().BeGreaterThan(1); - FixClock(); + FixClock(); - // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); - var rateLimiter = GetRateLimiter(onePer, bucketCapacity); + // Arrange + var onePerSeconds = 1; + var onePer = TimeSpan.FromSeconds(onePerSeconds); + var rateLimiter = GetRateLimiter(onePer, bucketCapacity); - // Arrange - spend the initial bucket capacity. - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); + // Arrange - spend the initial bucket capacity. + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); - // Arrange - advance multiple times enough to permit a full bucket burst - AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); + // Arrange - advance multiple times enough to permit a full bucket burst + AdvanceClock(onePer.Ticks * bucketCapacity * multipleRefillTimePassed); - // Assert - expect full bucket capacity but no more - rateLimiter.ShouldPermitNExecutions(bucketCapacity); - rateLimiter.ShouldNotPermitAnExecution(); - } + // Assert - expect full bucket capacity but no more + rateLimiter.ShouldPermitNExecutions(bucketCapacity); + rateLimiter.ShouldNotPermitAnExecution(); + } - [Theory] - [InlineData(2)] - [InlineData(5)] - [InlineData(100)] - public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) - { - FixClock(); + [Theory] + [InlineData(2)] + [InlineData(5)] + [InlineData(100)] + public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_one(int parallelContention) + { + FixClock(); - // Arrange - var onePer = TimeSpan.FromSeconds(1); - var rateLimiter = GetRateLimiter(onePer, 1); + // Arrange + var onePer = TimeSpan.FromSeconds(1); + var rateLimiter = GetRateLimiter(onePer, 1); - // Arrange - parallel tasks all waiting on a manual reset event. - var gate = new ManualResetEventSlim(); - Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; - for (var i = 0; i < parallelContention; i++) - { - tasks[i] = Task.Run(() => + // Arrange - parallel tasks all waiting on a manual reset event. + var gate = new ManualResetEventSlim(); + Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; + for (var i = 0; i < parallelContention; i++) { - gate.Wait(); - return rateLimiter.PermitExecution(); - }); + tasks[i] = Task.Run(() => + { + gate.Wait(); + return rateLimiter.PermitExecution(); + }); + } + + // Act - release gate. + gate.Set(); + Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); + + // Assert - one should have permitted execution, n-1 not. + var results = tasks.Select(t => t.Result).ToList(); + results.Count(r => r.permitExecution).Should().Be(1); + results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); } - - // Act - release gate. - gate.Set(); - Within(TimeSpan.FromSeconds(10 /* high to allow for slow-running on time-slicing CI servers */), () => tasks.All(t => t.IsCompleted).Should().BeTrue()); - - // Assert - one should have permitted execution, n-1 not. - var results = tasks.Select(t => t.Result).ToList(); - results.Count(r => r.permitExecution).Should().Be(1); - results.Count(r => !r.permitExecution).Should().Be(parallelContention - 1); } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs index 159ade9af88..feef9d4f0fb 100644 --- a/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs @@ -5,259 +5,260 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Registry; - -public class ConcurrentPolicyRegistrySpecs +namespace Polly.Specs.Registry { - IConcurrentPolicyRegistry _registry; - - public ConcurrentPolicyRegistrySpecs() - { - _registry = new PolicyRegistry(); - } - - [Fact] - public void Should_be_able_to_add_Policy_using_TryAdd() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - var insert = _registry.TryAdd(key, policy); - _registry.Count.Should().Be(1); - insert.Should().Be(true); - - Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); - - var insert2 = _registry.TryAdd(key2, policy2); - _registry.Count.Should().Be(2); - insert2.Should().Be(true); - } - - [Fact] - public void Should_be_able_to_add_PolicyTResult_using_TryAdd() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - var insert = _registry.TryAdd(key, policy); - _registry.Count.Should().Be(1); - insert.Should().Be(true); - - Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); - - var insert2 = _registry.TryAdd(key2, policy2); - _registry.Count.Should().Be(2); - insert2.Should().Be(true); - } - - [Fact] - public void Should_be_able_to_add_Policy_by_interface_using_TryAdd() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - var insert = _registry.TryAdd(key, policy); - _registry.Count.Should().Be(1); - insert.Should().Be(true); - - ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); - - var insert2 = _registry.TryAdd(key2, policy2); - _registry.Count.Should().Be(2); - insert2.Should().Be(true); - } - - [Fact] - public void Should_be_able_to_remove_policy_with_TryRemove() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - var removed = _registry.TryRemove(key, out IsPolicy removedPolicy); - _registry.Count.Should().Be(0); - removedPolicy.Should().BeSameAs(policy); - removed.Should().BeTrue(); - } - - [Fact] - public void Should_report_false_from_TryRemove_if_no_Policy() + public class ConcurrentPolicyRegistrySpecs { - var key = Guid.NewGuid().ToString(); + IConcurrentPolicyRegistry _registry; + + public ConcurrentPolicyRegistrySpecs() + { + _registry = new PolicyRegistry(); + } + + [Fact] + public void Should_be_able_to_add_Policy_using_TryAdd() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + var insert = _registry.TryAdd(key, policy); + _registry.Count.Should().Be(1); + insert.Should().Be(true); + + Policy policy2 = Policy.NoOp(); + var key2 = Guid.NewGuid().ToString(); + + var insert2 = _registry.TryAdd(key2, policy2); + _registry.Count.Should().Be(2); + insert2.Should().Be(true); + } + + [Fact] + public void Should_be_able_to_add_PolicyTResult_using_TryAdd() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + var insert = _registry.TryAdd(key, policy); + _registry.Count.Should().Be(1); + insert.Should().Be(true); + + Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key2 = Guid.NewGuid().ToString(); + + var insert2 = _registry.TryAdd(key2, policy2); + _registry.Count.Should().Be(2); + insert2.Should().Be(true); + } + + [Fact] + public void Should_be_able_to_add_Policy_by_interface_using_TryAdd() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + var insert = _registry.TryAdd(key, policy); + _registry.Count.Should().Be(1); + insert.Should().Be(true); + + ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key2 = Guid.NewGuid().ToString(); + + var insert2 = _registry.TryAdd(key2, policy2); + _registry.Count.Should().Be(2); + insert2.Should().Be(true); + } + + [Fact] + public void Should_be_able_to_remove_policy_with_TryRemove() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - var removed = _registry.TryRemove(key, out IsPolicy removedPolicy); - removed.Should().BeFalse(); - } + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + var removed = _registry.TryRemove(key, out IsPolicy removedPolicy); + _registry.Count.Should().Be(0); + removedPolicy.Should().BeSameAs(policy); + removed.Should().BeTrue(); + } + + [Fact] + public void Should_report_false_from_TryRemove_if_no_Policy() + { + var key = Guid.NewGuid().ToString(); - [Fact] - public void Should_be_able_to_update_policy_with_TryUpdate() - { - Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + var removed = _registry.TryRemove(key, out IsPolicy removedPolicy); + removed.Should().BeFalse(); + } + + [Fact] + public void Should_be_able_to_update_policy_with_TryUpdate() + { + Policy existingPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); + + Policy newPolicy = Policy.NoOp(); - Policy newPolicy = Policy.NoOp(); + var updated = _registry.TryUpdate(key, newPolicy, existingPolicy); + + updated.Should().BeTrue(); + _registry[key].Should().BeSameAs(newPolicy); + } + + [Fact] + public void Should_not_update_policy_with_TryUpdate_when_existingPolicy_mismatch() + { + Policy existingPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - var updated = _registry.TryUpdate(key, newPolicy, existingPolicy); + var someOtherPolicy = Policy.NoOp(); + Policy newPolicy = Policy.NoOp(); - updated.Should().BeTrue(); - _registry[key].Should().BeSameAs(newPolicy); - } + var updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); - [Fact] - public void Should_not_update_policy_with_TryUpdate_when_existingPolicy_mismatch() - { - Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + updated.Should().BeFalse(); + _registry[key].Should().BeSameAs(existingPolicy); + } - var someOtherPolicy = Policy.NoOp(); - Policy newPolicy = Policy.NoOp(); + [Fact] + public void Should_not_update_policy_with_TryUpdate_when_no_existing_value() + { + var key = Guid.NewGuid().ToString(); - var updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); + var someOtherPolicy = Policy.NoOp(); + Policy newPolicy = Policy.NoOp(); - updated.Should().BeFalse(); - _registry[key].Should().BeSameAs(existingPolicy); - } + var updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); - [Fact] - public void Should_not_update_policy_with_TryUpdate_when_no_existing_value() - { - var key = Guid.NewGuid().ToString(); + updated.Should().BeFalse(); + _registry.ContainsKey(key).Should().BeFalse(); + } - var someOtherPolicy = Policy.NoOp(); - Policy newPolicy = Policy.NoOp(); + [Fact] + public void Should_add_with_GetOrAdd_with_value_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + var returnedPolicy = _registry.GetOrAdd(key, newPolicy); - var updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); + returnedPolicy.Should().BeSameAs(newPolicy); + } - updated.Should().BeFalse(); - _registry.ContainsKey(key).Should().BeFalse(); - } + [Fact] + public void Should_add_with_GetOrAdd_with_factory_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - [Fact] - public void Should_add_with_GetOrAdd_with_value_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); - var returnedPolicy = _registry.GetOrAdd(key, newPolicy); + returnedPolicy.Should().BeSameAs(newPolicy); + } - returnedPolicy.Should().BeSameAs(newPolicy); - } + [Fact] + public void Should_return_existing_with_GetOrAdd_with_value_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - [Fact] - public void Should_add_with_GetOrAdd_with_factory_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + Policy newPolicy = Policy.NoOp(); - var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); + var returnedPolicy = _registry.GetOrAdd(key, newPolicy); - returnedPolicy.Should().BeSameAs(newPolicy); - } + returnedPolicy.Should().BeSameAs(existingPolicy); + } - [Fact] - public void Should_return_existing_with_GetOrAdd_with_value_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + [Fact] + public void Should_return_existing_with_GetOrAdd_with_factory_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - Policy newPolicy = Policy.NoOp(); + Policy newPolicy = Policy.NoOp(); - var returnedPolicy = _registry.GetOrAdd(key, newPolicy); + var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); - returnedPolicy.Should().BeSameAs(existingPolicy); - } + returnedPolicy.Should().BeSameAs(existingPolicy); + } - [Fact] - public void Should_return_existing_with_GetOrAdd_with_factory_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + [Fact] + public void Should_add_with_AddOrUpdate_with_value_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - Policy newPolicy = Policy.NoOp(); + var returnedPolicy = _registry.AddOrUpdate( + key, + newPolicy, + (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); - var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); + returnedPolicy.Should().BeSameAs(newPolicy); + } - returnedPolicy.Should().BeSameAs(existingPolicy); - } + [Fact] + public void Should_add_with_AddOrUpdate_with_addfactory_when_no_existing_policy() + { + Policy newPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - [Fact] - public void Should_add_with_AddOrUpdate_with_value_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - var returnedPolicy = _registry.AddOrUpdate( - key, - newPolicy, - (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); + var returnedPolicy = _registry.AddOrUpdate( + key, + _ => newPolicy, + (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); - returnedPolicy.Should().BeSameAs(newPolicy); - } + returnedPolicy.Should().BeSameAs(newPolicy); + } - [Fact] - public void Should_add_with_AddOrUpdate_with_addfactory_when_no_existing_policy() - { - Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addvalue_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); + const string policyKeyToDecorate = "SomePolicyKey"; - var returnedPolicy = _registry.AddOrUpdate( - key, - _ => newPolicy, - (_, _) => throw new InvalidOperationException("Update factory should not be called in this test.")); + Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); - returnedPolicy.Should().BeSameAs(newPolicy); - } + var returnedPolicy = _registry.AddOrUpdate( + key, + otherPolicyNotExpectingToAdd, + (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); - [Fact] - public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addvalue_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); + returnedPolicy.Should().BeSameAs(existingPolicy); + returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); + } - const string policyKeyToDecorate = "SomePolicyKey"; + [Fact] + public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addfactory_when_existing_policy() + { + Policy existingPolicy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + _registry.Add(key, existingPolicy); - Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); + const string policyKeyToDecorate = "SomePolicyKey"; - var returnedPolicy = _registry.AddOrUpdate( - key, - otherPolicyNotExpectingToAdd, - (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); + Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); - returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); - returnedPolicy.Should().BeSameAs(existingPolicy); - returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); - } - - [Fact] - public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addfactory_when_existing_policy() - { - Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - _registry.Add(key, existingPolicy); + var returnedPolicy = _registry.AddOrUpdate( + key, + _ => otherPolicyNotExpectingToAdd, + (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); - const string policyKeyToDecorate = "SomePolicyKey"; + returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); + returnedPolicy.Should().BeSameAs(existingPolicy); + returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); + } - Policy otherPolicyNotExpectingToAdd = Policy.Handle().Retry(); - - var returnedPolicy = _registry.AddOrUpdate( - key, - _ => otherPolicyNotExpectingToAdd, - (_, _) => existingPolicy.WithPolicyKey(policyKeyToDecorate)); - - returnedPolicy.Should().NotBeSameAs(otherPolicyNotExpectingToAdd); - returnedPolicy.Should().BeSameAs(existingPolicy); - returnedPolicy.PolicyKey.Should().Be(policyKeyToDecorate); } - -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs index e32594dec32..93731cc5174 100644 --- a/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs @@ -8,465 +8,466 @@ using Polly.Specs.Helpers; using Moq; -namespace Polly.Specs.Registry; - -public class PolicyRegistrySpecs +namespace Polly.Specs.Registry { - IPolicyRegistry _registry; - - public PolicyRegistrySpecs() + public class PolicyRegistrySpecs { - _registry = new PolicyRegistry(); - } + IPolicyRegistry _registry; - #region Tests for adding Policy + public PolicyRegistrySpecs() + { + _registry = new PolicyRegistry(); + } - [Fact] - public void Should_be_able_to_add_Policy_using_Add() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + #region Tests for adding Policy - _registry.Add(key, policy); - _registry.Count.Should().Be(1); + [Fact] + public void Should_be_able_to_add_Policy_using_Add() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); + _registry.Add(key, policy); + _registry.Count.Should().Be(1); - _registry.Add(key2, policy2); - _registry.Count.Should().Be(2); - } + Policy policy2 = Policy.NoOp(); + var key2 = Guid.NewGuid().ToString(); - [Fact] - public void Should_be_able_to_add_PolicyTResult_using_Add() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); + _registry.Add(key2, policy2); + _registry.Count.Should().Be(2); + } - Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); - - _registry.Add(key2, policy2); - _registry.Count.Should().Be(2); - } - - [Fact] - public void Should_be_able_to_add_Policy_by_interface_using_Add() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); - - _registry.Add>(key2, policy2); - _registry.Count.Should().Be(2); - } - - [Fact] - public void Should_be_able_to_add_Policy_using_Indexer() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_add_PolicyTResult_using_Add() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - _registry[key] = policy; - _registry.Count.Should().Be(1); + _registry.Add(key, policy); + _registry.Count.Should().Be(1); - Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); + Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key2 = Guid.NewGuid().ToString(); - _registry[key2] = policy2; - _registry.Count.Should().Be(2); - } + _registry.Add(key2, policy2); + _registry.Count.Should().Be(2); + } - [Fact] - public void Should_be_able_to_add_PolicyTResult_using_Indexer() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_add_Policy_by_interface_using_Add() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - _registry[key] = policy; - _registry.Count.Should().Be(1); + _registry.Add(key, policy); + _registry.Count.Should().Be(1); - Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); + ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key2 = Guid.NewGuid().ToString(); - _registry[key2] = policy2; - _registry.Count.Should().Be(2); - } + _registry.Add>(key2, policy2); + _registry.Count.Should().Be(2); + } - [Fact] - public void Should_not_be_able_to_add_Policy_with_duplicate_key_using_Add() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_add_Policy_using_Indexer() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - _registry.Invoking(r => r.Add(key, policy)) - .Should().NotThrow(); + _registry[key] = policy; + _registry.Count.Should().Be(1); - _registry.Invoking(r => r.Add(key, policy)) - .Should().Throw(); + Policy policy2 = Policy.NoOp(); + var key2 = Guid.NewGuid().ToString(); - _registry.Count.Should().Be(1); - } + _registry[key2] = policy2; + _registry.Count.Should().Be(2); + } - [Fact] - public void Should_be_able_to_overwrite_existing_Policy_if_key_exists_when_inserting_using_Indexer() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - - Policy policy_new = Policy.NoOp(); - _registry[key] = policy_new; + [Fact] + public void Should_be_able_to_add_PolicyTResult_using_Indexer() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - _registry.Count.Should().Be(1); + _registry[key] = policy; + _registry.Count.Should().Be(1); - _registry.Get(key).Should().BeSameAs(policy_new); - } + Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key2 = Guid.NewGuid().ToString(); - [Fact] - public void Should_be_able_to_overwrite_existing_PolicyTResult_if_key_exists_when_inserting_using_Indexer() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); - _registry.Add>(key, policy); - - Policy policy_new = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - _registry[key] = policy_new; - - _registry.Count.Should().Be(1); - - _registry.Get>(key).Should().BeSameAs(policy_new); - } + _registry[key2] = policy2; + _registry.Count.Should().Be(2); + } - [Fact] - public void Should_throw_when_adding_Policy_using_Add_when_key_is_null() - { - string key = null; - Policy policy = Policy.NoOp(); - _registry.Invoking(r => r.Add(key, policy)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_adding_Policy_using_Indexer_when_key_is_null() - { - string key = null; - Policy policy = Policy.NoOp(); - _registry.Invoking(r => r[key] = policy) - .Should().Throw(); - } - - #endregion - - #region Tests for retrieving policy - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - - _registry.Add(key, policy); - _registry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - - _registry.Add(key, policy); - _registry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; - - _registry.Add(key, policy); - _registry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Get() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_not_be_able_to_add_Policy_with_duplicate_key_using_Add() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - _registry.Get(key).Should().BeSameAs(policy); - } + _registry.Invoking(r => r.Add(key, policy)) + .Should().NotThrow(); - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + _registry.Invoking(r => r.Add(key, policy)) + .Should().Throw(); - _registry.Add(key, policy); - _registry.Get>(key).Should().BeSameAs(policy); - } + _registry.Count.Should().Be(1); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_overwrite_existing_Policy_if_key_exists_when_inserting_using_Indexer() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + _registry.Add(key, policy); - _registry.Add(key, policy); - _registry.Get>(key).Should().BeSameAs(policy); - } + Policy policy_new = Policy.NoOp(); + _registry[key] = policy_new; - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + _registry.Count.Should().Be(1); - _registry.Add(key, policy); - _registry[key].Should().BeSameAs(policy); - } + _registry.Get(key).Should().BeSameAs(policy_new); + } - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_overwrite_existing_PolicyTResult_if_key_exists_when_inserting_using_Indexer() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + _registry.Add>(key, policy); - _registry.Add(key, policy); - _registry[key].Should().BeSameAs(policy); - } + Policy policy_new = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + _registry[key] = policy_new; - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry[key].Should().BeSameAs(policy); - } - - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() - { - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - var result = false; - - _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); - - result.Should().BeFalse(); - } + _registry.Count.Should().Be(1); - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() - { - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - var result = false; - - _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); - - result.Should().BeFalse(); - } - - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() - { - var key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; - var result = false; - - _registry.Invoking(r => result = r.TryGet>(key, out outPolicy)) - .Should().NotThrow(); - - result.Should().BeFalse(); - } - - [Fact] - public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() - { - var key = Guid.NewGuid().ToString(); - Policy policy = null; - _registry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() - { - var key = Guid.NewGuid().ToString(); - Policy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() - { - var key = Guid.NewGuid().ToString(); - ISyncPolicy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() - { - var key = Guid.NewGuid().ToString(); - IsPolicy outPolicy = null; - _registry.Invoking(r => outPolicy = r[key]) - .Should().Throw(); - } - - - [Fact] - public void Should_throw_when_retrieving_using_Get_when_key_is_null() - { - string key = null; - Policy policy = null; - _registry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() - { - string key = null; - Policy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() - { - string key = null; - ISyncPolicy policy = null; - _registry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() - { - string key = null; - IsPolicy policy = null; - _registry.Invoking(r => policy = r[key]) - .Should().Throw(); - } - #endregion - - #region Tests for removing policy - [Fact] - public void Should_be_able_to_clear_registry() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); - - _registry.Add(key2, policy2); - _registry.Count.Should().Be(2); - - _registry.Clear(); - _registry.Count.Should().Be(0); - } - - [Fact] - public void Should_be_able_to_remove_policy() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.Count.Should().Be(1); - - _registry.Remove(key); - _registry.Count.Should().Be(0); - } - - [Fact] - public void Should_throw_when_removing_Policy_when_key_is_null() - { - string key = null; - _registry.Invoking(r => r.Remove(key)) - .Should().Throw(); - } - #endregion - - #region Tests for checking if key exists - - [Fact] - public void Should_be_able_to_check_if_key_exists() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - - _registry.Add(key, policy); - _registry.ContainsKey(key).Should().BeTrue(); - - var key2 = Guid.NewGuid().ToString(); - _registry.ContainsKey(key2).Should().BeFalse(); - } - - [Fact] - public void Should_throw_when_checking_if_key_exists_when_key_is_null() - { - string key = null; - _registry.Invoking(r => r.ContainsKey(key)) - .Should().Throw(); - } - #endregion - - #region Tests for the constructor - [Fact] - public void Constructor_Called_With_A_Registry_Parameter_Should_Assign_The_Passed_In_Registry_To_The_Registry_Field() - { - var testDictionary = new Mock>(); - var testRegistry = new PolicyRegistry(testDictionary.Object); - - //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. - var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); - var registryFieldValue = registryField.GetValue(testRegistry); - registryFieldValue.Should().Be(testDictionary.Object); - } - - [Fact] - public void Constructor_Called_With_Default_Parameters_Assigns_A_ConcurrentDictionary_Of_TKey_And_IsPolicy_To_The_Private_Registry_Field() - { - var expectedDictionaryType = typeof(ConcurrentDictionary); - var testRegistry = new PolicyRegistry(); - - //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. - var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); - var registryFieldValue = registryField.GetValue(testRegistry); - registryFieldValue.Should().BeOfType(expectedDictionaryType); + _registry.Get>(key).Should().BeSameAs(policy_new); + } + + [Fact] + public void Should_throw_when_adding_Policy_using_Add_when_key_is_null() + { + string key = null; + Policy policy = Policy.NoOp(); + _registry.Invoking(r => r.Add(key, policy)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_adding_Policy_using_Indexer_when_key_is_null() + { + string key = null; + Policy policy = Policy.NoOp(); + _registry.Invoking(r => r[key] = policy) + .Should().Throw(); + } + + #endregion + + #region Tests for retrieving policy + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + + _registry.Add(key, policy); + _registry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + + _registry.Add(key, policy); + _registry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; + + _registry.Add(key, policy); + _registry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Get() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Get(key).Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Get>(key).Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Get>(key).Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry[key].Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry[key].Should().BeSameAs(policy); + } + + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry[key].Should().BeSameAs(policy); + } + + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() + { + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + var result = false; + + _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); + + result.Should().BeFalse(); + } + + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() + { + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + var result = false; + + _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); + + result.Should().BeFalse(); + } + + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() + { + var key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; + var result = false; + + _registry.Invoking(r => result = r.TryGet>(key, out outPolicy)) + .Should().NotThrow(); + + result.Should().BeFalse(); + } + + [Fact] + public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() + { + var key = Guid.NewGuid().ToString(); + Policy policy = null; + _registry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() + { + var key = Guid.NewGuid().ToString(); + Policy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() + { + var key = Guid.NewGuid().ToString(); + ISyncPolicy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() + { + var key = Guid.NewGuid().ToString(); + IsPolicy outPolicy = null; + _registry.Invoking(r => outPolicy = r[key]) + .Should().Throw(); + } + + + [Fact] + public void Should_throw_when_retrieving_using_Get_when_key_is_null() + { + string key = null; + Policy policy = null; + _registry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() + { + string key = null; + Policy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() + { + string key = null; + ISyncPolicy policy = null; + _registry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() + { + string key = null; + IsPolicy policy = null; + _registry.Invoking(r => policy = r[key]) + .Should().Throw(); + } + #endregion + + #region Tests for removing policy + [Fact] + public void Should_be_able_to_clear_registry() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + Policy policy2 = Policy.NoOp(); + var key2 = Guid.NewGuid().ToString(); + + _registry.Add(key2, policy2); + _registry.Count.Should().Be(2); + + _registry.Clear(); + _registry.Count.Should().Be(0); + } + + [Fact] + public void Should_be_able_to_remove_policy() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.Count.Should().Be(1); + + _registry.Remove(key); + _registry.Count.Should().Be(0); + } + + [Fact] + public void Should_throw_when_removing_Policy_when_key_is_null() + { + string key = null; + _registry.Invoking(r => r.Remove(key)) + .Should().Throw(); + } + #endregion + + #region Tests for checking if key exists + + [Fact] + public void Should_be_able_to_check_if_key_exists() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + + _registry.Add(key, policy); + _registry.ContainsKey(key).Should().BeTrue(); + + var key2 = Guid.NewGuid().ToString(); + _registry.ContainsKey(key2).Should().BeFalse(); + } + + [Fact] + public void Should_throw_when_checking_if_key_exists_when_key_is_null() + { + string key = null; + _registry.Invoking(r => r.ContainsKey(key)) + .Should().Throw(); + } + #endregion + + #region Tests for the constructor + [Fact] + public void Constructor_Called_With_A_Registry_Parameter_Should_Assign_The_Passed_In_Registry_To_The_Registry_Field() + { + var testDictionary = new Mock>(); + var testRegistry = new PolicyRegistry(testDictionary.Object); + + //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. + var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); + var registryFieldValue = registryField.GetValue(testRegistry); + registryFieldValue.Should().Be(testDictionary.Object); + } + + [Fact] + public void Constructor_Called_With_Default_Parameters_Assigns_A_ConcurrentDictionary_Of_TKey_And_IsPolicy_To_The_Private_Registry_Field() + { + var expectedDictionaryType = typeof(ConcurrentDictionary); + var testRegistry = new PolicyRegistry(); + + //Generally, using reflection is a bad practice, but we are accepting it given we own the implementation. + var registryField = typeof(PolicyRegistry).GetField("_registry", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance); + var registryFieldValue = registryField.GetValue(testRegistry); + registryFieldValue.Should().BeOfType(expectedDictionaryType); + } + + #endregion } - - #endregion } \ No newline at end of file diff --git a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs index 41c01a764e4..99650c09607 100644 --- a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs @@ -6,283 +6,284 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Registry; - -public class ReadOnlyPolicyRegistrySpecs +namespace Polly.Specs.Registry { - IPolicyRegistry _registry; - - IReadOnlyPolicyRegistry ReadOnlyRegistry { get{ return _registry; } } - - public ReadOnlyPolicyRegistrySpecs() + public class ReadOnlyPolicyRegistrySpecs { - _registry = new PolicyRegistry(); - } - - #region Tests for retrieving policy + IPolicyRegistry _registry; - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; + IReadOnlyPolicyRegistry ReadOnlyRegistry { get{ return _registry; } } - _registry.Add(key, policy); - ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - - _registry.Add(key, policy); - ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } + public ReadOnlyPolicyRegistrySpecs() + { + _registry = new PolicyRegistry(); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; + #region Tests for retrieving policy - _registry.Add(key, policy); - ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); - outPolicy.Should().BeSameAs(policy); - } - - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Get() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; - _registry.Add(key, policy); - ReadOnlyRegistry.Get(key).Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; - _registry.Add(key, policy); - ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; - _registry.Add(key, policy); - ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.TryGet(key, out outPolicy).Should().BeTrue(); + outPolicy.Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Get() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry[key].Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.Get(key).Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() - { - Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry[key].Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); + } - [Fact] - public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() - { - ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry[key].Should().BeSameAs(policy); - } + _registry.Add(key, policy); + ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); + } - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() - { - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - var result = false; + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); + _registry.Add(key, policy); + ReadOnlyRegistry[key].Should().BeSameAs(policy); + } - result.Should().BeFalse(); - } + [Fact] + public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() + { + Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() - { - var key = Guid.NewGuid().ToString(); - Policy outPolicy = null; - var result = false; + _registry.Add(key, policy); + ReadOnlyRegistry[key].Should().BeSameAs(policy); + } - ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) - .Should().NotThrow(); + [Fact] + public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() + { + ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); + var key = Guid.NewGuid().ToString(); - result.Should().BeFalse(); - } + _registry.Add(key, policy); + ReadOnlyRegistry[key].Should().BeSameAs(policy); + } - [Fact] - public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() - { - var key = Guid.NewGuid().ToString(); - ISyncPolicy outPolicy = null; - var result = false; + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() + { + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + var result = false; - ReadOnlyRegistry.Invoking(r => result = r.TryGet>(key, out outPolicy)) - .Should().NotThrow(); + ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); - result.Should().BeFalse(); - } + result.Should().BeFalse(); + } - [Fact] - public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() - { - var key = Guid.NewGuid().ToString(); - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() + { + var key = Guid.NewGuid().ToString(); + Policy outPolicy = null; + var result = false; - [Fact] - public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() - { - var key = Guid.NewGuid().ToString(); - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } + ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) + .Should().NotThrow(); - [Fact] - public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() - { - var key = Guid.NewGuid().ToString(); - ISyncPolicy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } + result.Should().BeFalse(); + } - [Fact] - public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() - { - var key = Guid.NewGuid().ToString(); - IsPolicy outPolicy = null; - ReadOnlyRegistry.Invoking(r => outPolicy = r[key]) - .Should().Throw(); - } + [Fact] + public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() + { + var key = Guid.NewGuid().ToString(); + ISyncPolicy outPolicy = null; + var result = false; + ReadOnlyRegistry.Invoking(r => result = r.TryGet>(key, out outPolicy)) + .Should().NotThrow(); - [Fact] - public void Should_throw_when_retrieving_using_Get_when_key_is_null() - { - string key = null; - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) - .Should().Throw(); - } + result.Should().BeFalse(); + } - [Fact] - public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() - { - string key = null; - Policy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } + [Fact] + public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() + { + var key = Guid.NewGuid().ToString(); + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() + { + var key = Guid.NewGuid().ToString(); + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() + { + var key = Guid.NewGuid().ToString(); + ISyncPolicy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() + { + var key = Guid.NewGuid().ToString(); + IsPolicy outPolicy = null; + ReadOnlyRegistry.Invoking(r => outPolicy = r[key]) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() - { - string key = null; - ISyncPolicy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) - .Should().Throw(); - } - [Fact] - public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() - { - string key = null; - IsPolicy policy = null; - ReadOnlyRegistry.Invoking(r => policy = r[key]) - .Should().Throw(); - } - #endregion + [Fact] + public void Should_throw_when_retrieving_using_Get_when_key_is_null() + { + string key = null; + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_GetTResult_when_key_is_null() + { + string key = null; + Policy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_Get_by_interface_when_key_is_null() + { + string key = null; + ISyncPolicy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() + { + string key = null; + IsPolicy policy = null; + ReadOnlyRegistry.Invoking(r => policy = r[key]) + .Should().Throw(); + } + #endregion - #region Tests for checking if key exists + #region Tests for checking if key exists - [Fact] - public void Should_be_able_to_check_if_key_exists() - { - Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + [Fact] + public void Should_be_able_to_check_if_key_exists() + { + Policy policy = Policy.NoOp(); + var key = Guid.NewGuid().ToString(); - _registry.Add(key, policy); - ReadOnlyRegistry.ContainsKey(key).Should().BeTrue(); + _registry.Add(key, policy); + ReadOnlyRegistry.ContainsKey(key).Should().BeTrue(); - var key2 = Guid.NewGuid().ToString(); - ReadOnlyRegistry.ContainsKey(key2).Should().BeFalse(); - } + var key2 = Guid.NewGuid().ToString(); + ReadOnlyRegistry.ContainsKey(key2).Should().BeFalse(); + } - [Fact] - public void Should_throw_when_checking_if_key_exists_when_key_is_null() - { - string key = null; - ReadOnlyRegistry.Invoking(r => r.ContainsKey(key)) - .Should().Throw(); - } - #endregion + [Fact] + public void Should_throw_when_checking_if_key_exists_when_key_is_null() + { + string key = null; + ReadOnlyRegistry.Invoking(r => r.ContainsKey(key)) + .Should().Throw(); + } + #endregion - #region Tests for the GetEnumerator method - - [Fact] - public void Calling_The_GetEnumerator_Method_Returning_A_IEnumerator_Of_KeyValuePair_Of_String_And_IsPolicy_Calls_The_Registrys_GetEnumerator_Method() - { - var testDictionary = new Mock>(); - var testRegistry = new PolicyRegistry(testDictionary.Object); + #region Tests for the GetEnumerator method - testRegistry.GetEnumerator(); + [Fact] + public void Calling_The_GetEnumerator_Method_Returning_A_IEnumerator_Of_KeyValuePair_Of_String_And_IsPolicy_Calls_The_Registrys_GetEnumerator_Method() + { + var testDictionary = new Mock>(); + var testRegistry = new PolicyRegistry(testDictionary.Object); - testDictionary.Verify(x => x.GetEnumerator(), Times.Once); - } + testRegistry.GetEnumerator(); - #endregion + testDictionary.Verify(x => x.GetEnumerator(), Times.Once); + } - #region Collection initializer tests + #endregion - [Fact] - public void Policies_Should_Be_Added_To_The_Registry_When_Using_Collection_Initializer_Syntax() - { - var key = Guid.NewGuid().ToString(); - var policy = Policy.NoOp(); + #region Collection initializer tests - var testRegistry = new PolicyRegistry + [Fact] + public void Policies_Should_Be_Added_To_The_Registry_When_Using_Collection_Initializer_Syntax() { - {key, policy} - }; + var key = Guid.NewGuid().ToString(); + var policy = Policy.NoOp(); + + var testRegistry = new PolicyRegistry + { + {key, policy} + }; - testRegistry.Should().Equal(new KeyValuePair(key, policy)); + testRegistry.Should().Equal(new KeyValuePair(key, policy)); + } + #endregion } - #endregion -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs index da82c2364a8..a9dfe62f9e8 100644 --- a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs @@ -11,746 +11,747 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry; - -public class RetryAsyncSpecs +namespace Polly.Specs.Retry { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() + public class RetryAsyncSpecs { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .RetryAsync(-1, onRetry); + Action policy = () => Policy + .Handle() + .RetryAsync(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .RetryAsync(3); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(3); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .RetryAsync(3); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(3); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .RetryAsync(3); + [Fact] + public void Should_throw_when_specified_exception_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(3); + [Fact] + public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .RetryAsync(); + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .RetryAsync(); + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .RetryAsync(); + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .RetryAsync(); + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .RetryAsync(); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryAsync(); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .Handle() - .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); - await policy.RaiseExceptionAsync(3); + await policy.RaiseExceptionAsync(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - var policy = Policy - .Handle() - .RetryAsync(3, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .RetryAsync(3, (exception, _) => retryExceptions.Add(exception)); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public async Task Should_call_onretry_with_a_handled_innerexception() - { - Exception passedToOnRetry = null; + [Fact] + public async Task Should_call_onretry_with_a_handled_innerexception() + { + Exception passedToOnRetry = null; - var policy = Policy - .HandleInner() - .RetryAsync(3, (exception, _) => passedToOnRetry = exception); + var policy = Policy + .HandleInner() + .RetryAsync(3, (exception, _) => passedToOnRetry = exception); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - await policy.RaiseExceptionAsync(withInner); + await policy.RaiseExceptionAsync(withInner); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - var policy = Policy - .Handle() - .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - retryCounts.Should() - .BeEmpty(); - } + retryCounts.Should() + .BeEmpty(); + } - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .RetryAsync(); + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .RetryAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextData = context); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextData = context); - policy.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextData = context); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextData = context); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => { throw new DivideByZeroException(); }, + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => { throw new DivideByZeroException(); }, new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_data() - { - Context capturedContext = null; + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_data() + { + Context capturedContext = null; - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => capturedContext = context); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => capturedContext = context); - policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); - capturedContext.Should() - .BeEmpty(); - } + capturedContext.Should() + .BeEmpty(); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() + ); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + [Fact] + public void Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - var policy = Policy - .Handle() - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); + var policy = Policy + .Handle() + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), new { key = "original_value" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), + policy.Awaiting(p => p.ExecuteAndCaptureAsync(_ => throw new DivideByZeroException(), new { key = "new_value" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero() - { - var retryInvoked = false; + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero() + { + var retryInvoked = false; - Action onRetry = (_, _) => { retryInvoked = true; }; + Action onRetry = (_, _) => { retryInvoked = true; }; - var policy = Policy - .Handle() - .RetryAsync(0, onRetry); + var policy = Policy + .Handle() + .RetryAsync(0, onRetry); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - retryInvoked.Should().BeFalse(); - } + retryInvoked.Should().BeFalse(); + } - #region Async and cancellation tests + #region Async and cancellation tests - [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning to Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' of the retry policy would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + [Fact] + public void Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning to Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' of the retry policy would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + + var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var executeDelegateInvocations = 0; + var executeDelegateInvocationsWhenOnRetryExits = 0; - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + var policy = Policy + .Handle() + .RetryAsync(async (_, _) => + { + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); - var policy = Policy - .Handle() - .RetryAsync(async (_, _) => + policy.Awaiting(p => p.ExecuteAsync(async () => { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + throw new DivideByZeroException(); + })).Should().Throw(); - policy.Awaiting(p => p.ExecuteAsync(async () => + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } + + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - throw new DivideByZeroException(); - })).Should().Throw(); + var policy = Policy + .Handle() + .RetryAsync(3); - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new Scenario + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .RetryAsync(3); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var policy = Policy + .Handle() + .RetryAsync(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - - var policy = Policy - .Handle() - .RetryAsync(3, (_, _) => + var scenario = new Scenario { - cancellationTokenSource.Cancel(); - }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + attemptsInvoked.Should().Be(1); + } - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .RetryAsync(3); - attemptsInvoked.Should().Be(1); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + bool? result = null; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - bool? result = null; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action) + .Should().NotThrow(); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + result.Should().BeTrue(); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().NotThrow(); + attemptsInvoked.Should().Be(1); + } - result.Should().BeTrue(); - - attemptsInvoked.Should().Be(1); - } + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .RetryAsync(3); - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var policy = Policy - .Handle() - .RetryAsync(3); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + bool? result = null; - bool? result = null; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + result.Should().Be(null); - result.Should().Be(null); + attemptsInvoked.Should().Be(1); + } - attemptsInvoked.Should().Be(1); + #endregion } - - #endregion } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs index 36244e7dee1..90cfe107c45 100644 --- a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs @@ -11,519 +11,520 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry; - -public class RetryForeverAsyncSpecs +namespace Polly.Specs.Retry { - [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + public class RetryForeverAsyncSpecs { - var policy = Policy - .Handle() - .RetryForeverAsync(); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .RetryForeverAsync(); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .RetryForeverAsync(); + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .RetryForeverAsync(); + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .RetryForeverAsync(); + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .RetryForeverAsync(); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryForeverAsync(); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] {"Exception #1", "Exception #2", "Exception #3"}; - var retryExceptions = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] {"Exception #1", "Exception #2", "Exception #3"}; + var retryExceptions = new List(); - var policy = Policy - .Handle() - .RetryForeverAsync(exception => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .RetryForeverAsync(exception => retryExceptions.Add(exception)); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_on_each_retry_with_the_passed_context() + { + IDictionary contextData = null; - var policy = Policy - .Handle() - .RetryForeverAsync((_, context) => contextData = context); + var policy = Policy + .Handle() + .RetryForeverAsync((_, context) => contextData = context); - policy.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .Handle() - .RetryForeverAsync((_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .RetryForeverAsync((_, retryCount) => retryCounts.Add(retryCount)); - policy.RaiseExceptionAsync(3); + policy.RaiseExceptionAsync(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_data() - { - Context capturedContext = null; + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_data() + { + Context capturedContext = null; - var policy = Policy - .Handle() - .RetryForeverAsync((_, context) => capturedContext = context); + var policy = Policy + .Handle() + .RetryForeverAsync((_, context) => capturedContext = context); - policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); - capturedContext.Should() - .BeEmpty(); - } + capturedContext.Should() + .BeEmpty(); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - var policy = Policy - .Handle() - .RetryForeverAsync((_, context) => contextValue = context["key"].ToString()); + var policy = Policy + .Handle() + .RetryForeverAsync((_, context) => contextValue = context["key"].ToString()); - policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() + ); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); - var policy = Policy - .Handle() - .RetryForeverAsync(exception => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .RetryForeverAsync(exception => retryExceptions.Add(exception)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - retryExceptions.Should() - .BeEmpty(); - } + retryExceptions.Should() + .BeEmpty(); + } - [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + [Fact] + public void Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + var executeDelegateInvocations = 0; + var executeDelegateInvocationsWhenOnRetryExits = 0; - var policy = Policy - .Handle() - .RetryForeverAsync(async _ => + var policy = Policy + .Handle() + .RetryForeverAsync(async _ => + { + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); + + policy.Awaiting(p => p.ExecuteAsync(async () => { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } + })).Should().NotThrow(); + + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - policy.Awaiting(p => p.ExecuteAsync(async () => + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } - })).Should().NotThrow(); + var policy = Policy + .Handle() + .RetryForeverAsync(); - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + attemptsInvoked.Should().Be(0); + } - cancellationTokenSource.Cancel(); + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .RetryForeverAsync(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; - - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(2); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - - var policy = Policy - .Handle() - .RetryForeverAsync( + var policy = Policy + .Handle() + .RetryForeverAsync( _ => { cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().NotThrow(); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } + } } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/RetryForeverSpecs.cs b/src/Polly.Specs/Retry/RetryForeverSpecs.cs index 9dc83a3d7d0..811eaf9e97f 100644 --- a/src/Polly.Specs/Retry/RetryForeverSpecs.cs +++ b/src/Polly.Specs/Retry/RetryForeverSpecs.cs @@ -5,249 +5,250 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Retry; - -public class RetryForeverSpecs +namespace Polly.Specs.Retry { - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action nullOnRetry = null; - - Action policy = () => Policy - .Handle() - .RetryForever(nullOnRetry); - - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } - - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action nullOnRetry = null; - - Action policy = () => Policy - .Handle() - .RetryForever(nullOnRetry); - - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } - - [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .RetryForever(); - - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } - - [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .RetryForever(); - - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } - - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() - { - var policy = Policy - .Handle() - .RetryForeverAsync(); - - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } - - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .RetryForeverAsync(); - - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } - - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryForever(); - - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } - - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .RetryForeverAsync(); - - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } - - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] {"Exception #1", "Exception #2", "Exception #3"}; - var retryExceptions = new List(); - - var policy = Policy - .Handle() - .RetryForever(exception => retryExceptions.Add(exception)); - - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } - - [Fact] - public void Should_call_onretry_on_each_retry_with_the_passed_context() - { - IDictionary contextData = null; - - var policy = Policy - .Handle() - .RetryForever((_, context) => contextData = context); - - policy.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } - - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); - - var policy = Policy - .Handle() - .RetryForever((_, retryCount) => retryCounts.Add(retryCount)); - - policy.RaiseException(3); - - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } - - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); - - var policy = Policy - .Handle() - .RetryForever(exception => retryExceptions.Add(exception)); - - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - - retryExceptions.Should() - .BeEmpty(); - } - - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; - - var policy = Policy - .Handle() - .RetryForever((_, context) => contextValue = context["key"].ToString()); - - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); - - contextValue.Should().Be("original_value"); - - policy.RaiseException( - new { key = "new_value" }.AsDictionary() - ); - - contextValue.Should().Be("new_value"); + public class RetryForeverSpecs + { + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action nullOnRetry = null; + + Action policy = () => Policy + .Handle() + .RetryForever(nullOnRetry); + + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } + + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action nullOnRetry = null; + + Action policy = () => Policy + .Handle() + .RetryForever(nullOnRetry); + + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } + + [Fact] + public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .RetryForever(); + + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } + + [Fact] + public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .RetryForever(); + + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } + + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() + { + var policy = Policy + .Handle() + .RetryForeverAsync(); + + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } + + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .RetryForeverAsync(); + + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } + + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryForever(); + + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } + + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .RetryForeverAsync(); + + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } + + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] {"Exception #1", "Exception #2", "Exception #3"}; + var retryExceptions = new List(); + + var policy = Policy + .Handle() + .RetryForever(exception => retryExceptions.Add(exception)); + + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } + + [Fact] + public void Should_call_onretry_on_each_retry_with_the_passed_context() + { + IDictionary contextData = null; + + var policy = Policy + .Handle() + .RetryForever((_, context) => contextData = context); + + policy.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } + + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + + var policy = Policy + .Handle() + .RetryForever((_, retryCount) => retryCounts.Add(retryCount)); + + policy.RaiseException(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); + + var policy = Policy + .Handle() + .RetryForever(exception => retryExceptions.Add(exception)); + + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + + retryExceptions.Should() + .BeEmpty(); + } + + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; + + var policy = Policy + .Handle() + .RetryForever((_, context) => contextValue = context["key"].ToString()); + + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); + + contextValue.Should().Be("original_value"); + + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); + + contextValue.Should().Be("new_value"); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/RetrySpecs.cs b/src/Polly.Specs/Retry/RetrySpecs.cs index 243e4b8091c..4665da82b0d 100644 --- a/src/Polly.Specs/Retry/RetrySpecs.cs +++ b/src/Polly.Specs/Retry/RetrySpecs.cs @@ -6,857 +6,858 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Retry; - -public class RetrySpecs +namespace Polly.Specs.Retry { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() + public class RetrySpecs { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .Retry(-1, onRetry); + Action policy = () => Policy + .Handle() + .Retry(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .Retry(1, nullOnRetry); + Action policy = () => Policy + .Handle() + .Retry(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .Retry(-1, onRetry); + Action policy = () => Policy + .Handle() + .Retry(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .Retry(1, nullOnRetry); + Action policy = () => Policy + .Handle() + .Retry(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .Retry(3); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() - { - var policy = Policy - .Handle() - .Or() - .Retry(3); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_retry_count() + { + var policy = Policy + .Handle() + .Or() + .Retry(3); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .Retry(3); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() - { - var policy = Policy - .Handle() - .Or() - .Retry(3); - - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_retry_count() + { + var policy = Policy + .Handle() + .Or() + .Retry(3); + + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .Retry(3); + [Fact] + public void Should_throw_when_specified_exception_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() - { - var policy = Policy - .Handle() - .Or() - .Retry(3); - - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } + [Fact] + public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_then_retry_count() + { + var policy = Policy + .Handle() + .Or() + .Retry(3); + + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .Retry(); + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .Retry(); + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Retry(); + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .Retry(); + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Retry(); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .Retry(); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .Handle() - .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); - policy.RaiseException(3); + policy.RaiseException(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - var policy = Policy - .Handle() - .Retry(3, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .Retry(3, (exception, _) => retryExceptions.Add(exception)); - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_call_onretry_with_a_handled_innerexception() - { - Exception passedToOnRetry = null; + [Fact] + public void Should_call_onretry_with_a_handled_innerexception() + { + Exception passedToOnRetry = null; - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - Exception toRaiseAsInner = new DivideByZeroException(); - Exception withInner = new AggregateException(toRaiseAsInner); + Exception toRaiseAsInner = new DivideByZeroException(); + Exception withInner = new AggregateException(toRaiseAsInner); - policy.RaiseException(withInner); + policy.RaiseException(withInner); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_first_exception() - { - Exception passedToOnRetry = null; + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_first_exception() + { + Exception passedToOnRetry = null; - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception toRaiseAsInner = new DivideByZeroException(); - Exception aggregateException = new AggregateException( - new Exception("First: With Inner Exception", - toRaiseAsInner), - new Exception("Second: Without Inner Exception")); + Exception aggregateException = new AggregateException( + new Exception("First: With Inner Exception", + toRaiseAsInner), + new Exception("Second: Without Inner Exception")); - policy.RaiseException(aggregateException); + policy.RaiseException(aggregateException); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_second_exception() - { - Exception passedToOnRetry = null; + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_as_second_exception() + { + Exception passedToOnRetry = null; - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception toRaiseAsInner = new DivideByZeroException(); - Exception aggregateException = new AggregateException( - new Exception("First: Without Inner Exception"), - new Exception("Second: With Inner Exception", - toRaiseAsInner)); + Exception aggregateException = new AggregateException( + new Exception("First: Without Inner Exception"), + new Exception("Second: With Inner Exception", + toRaiseAsInner)); - policy.RaiseException(aggregateException); + policy.RaiseException(aggregateException); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_first_exception() - { - Exception passedToOnRetry = null; + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_first_exception() + { + Exception passedToOnRetry = null; - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception toRaiseAsInner = new DivideByZeroException(); - Exception aggregateException = new AggregateException( - new AggregateException( - new Exception("First: With Inner Exception", - toRaiseAsInner), - new Exception("Second: Without Inner Exception")), - new Exception("Exception")); + Exception aggregateException = new AggregateException( + new AggregateException( + new Exception("First: With Inner Exception", + toRaiseAsInner), + new Exception("Second: Without Inner Exception")), + new Exception("Exception")); - policy.RaiseException(aggregateException); + policy.RaiseException(aggregateException); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - [Fact] - public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_second_exception() - { - Exception passedToOnRetry = null; + [Fact] + public void Should_call_onretry_with_handled_exception_nested_in_aggregate_inside_another_aggregate_as_second_exception() + { + Exception passedToOnRetry = null; - var policy = Policy - .HandleInner() - .Retry(3, (exception, _) => passedToOnRetry = exception); + var policy = Policy + .HandleInner() + .Retry(3, (exception, _) => passedToOnRetry = exception); - Exception toRaiseAsInner = new DivideByZeroException(); + Exception toRaiseAsInner = new DivideByZeroException(); - Exception aggregateException = new AggregateException( - new Exception("Exception"), - new AggregateException( - new Exception("First: Without Inner Exception"), - new Exception("Second: With Inner Exception", - toRaiseAsInner))); + Exception aggregateException = new AggregateException( + new Exception("Exception"), + new AggregateException( + new Exception("First: Without Inner Exception"), + new Exception("Second: With Inner Exception", + toRaiseAsInner))); - policy.RaiseException(aggregateException); + policy.RaiseException(aggregateException); - passedToOnRetry.Should().BeSameAs(toRaiseAsInner); - } + passedToOnRetry.Should().BeSameAs(toRaiseAsInner); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - var policy = Policy - .Handle() - .Retry((_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .Retry((_, retryCount) => retryCounts.Add(retryCount)); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - retryCounts.Should() - .BeEmpty(); - } + retryCounts.Should() + .BeEmpty(); + } - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextData = context); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextData = context); - policy.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); + policy.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextData = context); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextData = context); - policy.Invoking(p => p.ExecuteAndCapture(_ => { throw new DivideByZeroException();}, + policy.Invoking(p => p.ExecuteAndCapture(_ => { throw new DivideByZeroException();}, new { key1 = "value1", key2 = "value2" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; - Policy policy = Policy - .Handle() - .Retry((_, _, context) => capturedContext = context); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => capturedContext = context); - policy.RaiseException(); + policy.RaiseException(); - capturedContext.Should() - .BeEmpty(); - } + capturedContext.Should() + .BeEmpty(); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextValue = context["key"].ToString()); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextValue = context["key"].ToString()); - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - policy.RaiseException( - new { key = "new_value" }.AsDictionary() - ); + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + [Fact] + public void Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - Policy policy = Policy - .Handle() - .Retry((_, _, context) => contextValue = context["key"].ToString()); + Policy policy = Policy + .Handle() + .Retry((_, _, context) => contextValue = context["key"].ToString()); - policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), + policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), new { key = "original_value" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), + policy.Invoking(p => p.ExecuteAndCapture(_ => throw new DivideByZeroException(), new { key = "new_value" }.AsDictionary())) - .Should().NotThrow(); + .Should().NotThrow(); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .Retry(); + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .Retry(); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - var retryInvoked = false; - - Action onRetry = (_, _) => { retryInvoked = true; }; + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_without_context() + { + var retryInvoked = false; - var policy = Policy - .Handle() - .Retry(0, onRetry); + Action onRetry = (_, _) => { retryInvoked = true; }; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + var policy = Policy + .Handle() + .Retry(0, onRetry); - retryInvoked.Should().BeFalse(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_context() - { - var retryInvoked = false; + retryInvoked.Should().BeFalse(); + } - Action onRetry = (_, _, _) => { retryInvoked = true; }; + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_context() + { + var retryInvoked = false; - var policy = Policy - .Handle() - .Retry(0, onRetry); + Action onRetry = (_, _, _) => { retryInvoked = true; }; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + var policy = Policy + .Handle() + .Retry(0, onRetry); - retryInvoked.Should().BeFalse(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + retryInvoked.Should().BeFalse(); + } - #region Sync cancellation tests - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .Retry(3); + #region Sync cancellation tests - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var policy = Policy + .Handle() + .Retry(3); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .Retry(3); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - - var policy = Policy - .Handle() - .Retry(3, (_, _) => + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { - cancellationTokenSource.Cancel(); - }); + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + attemptsInvoked.Should().Be(1 + 3); + } - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var policy = Policy + .Handle() + .Retry(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .Retry(3); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - result.Should().BeTrue(); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var policy = Policy - .Handle() - .Retry(3); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + result.Should().BeTrue(); - bool? result = null; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .Retry(3); - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - result.Should().Be(null); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(1); - } + bool? result = null; + + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - #endregion + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + result.Should().Be(null); + attemptsInvoked.Should().Be(1); + } + + #endregion + + + } } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs b/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs index c21859145cd..dcbd5e273fa 100644 --- a/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs +++ b/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs @@ -3,195 +3,196 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Retry; - -public class RetryTResultMixedResultExceptionSpecs +namespace Polly.Specs.Retry { - [Fact] - public void Should_handle_exception_when_TResult_policy_handling_exceptions_only() - { - Policy policy = Policy - .Handle().Retry(1); - - var result = policy.RaiseResultAndOrExceptionSequence(new DivideByZeroException(), ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_throw_unhandled_exception_when_TResult_policy_handling_exceptions_only() - { - Policy policy = Policy - .Handle().Retry(1); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_results_before_exceptions() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .Retry(2); - - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_exception_before_result() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Retry(2); - - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_exceptions_first() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Retry(4); - - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_results_first() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Retry(4); - - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Retry(3); - - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } - - [Fact] - public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() + public class RetryTResultMixedResultExceptionSpecs { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Or() - .Retry(3); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); - - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } - - [Fact] - public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Or() - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_return_unhandled_result_if_not_one_of_results_or_exceptions_specified() - { - Policy policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Or() - .Retry(2); - - var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain); - result.Should().Be(ResultPrimitive.FaultAgain); - } - - [Fact] - public void Should_throw_if_not_one_of_results_or_exceptions_handled() - { - Policy policy = Policy - .Handle() - .OrResult(ResultPrimitive.Fault) - .Retry(2); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) - .Should().Throw(); - } - - [Fact] - public void Should_handle_both_exceptions_and_specified_results_with_predicates() - { - Policy policy = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(2); - - var result = policy.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message","key"), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } - - [Fact] - public void Should_throw_if_exception_predicate_not_matched() - { - Policy policy = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(2); - - policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message", "value"), new ResultClass(ResultPrimitive.Good))) - .Should().Throw(); - } - - [Fact] - public void Should_return_unhandled_result_if_result_predicate_not_matched() - { - Policy policy = Policy - .Handle(e => e.ParamName == "key") - .OrResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(2); - - var result = policy.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"), new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); + [Fact] + public void Should_handle_exception_when_TResult_policy_handling_exceptions_only() + { + Policy policy = Policy + .Handle().Retry(1); + + var result = policy.RaiseResultAndOrExceptionSequence(new DivideByZeroException(), ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_unhandled_exception_when_TResult_policy_handling_exceptions_only() + { + Policy policy = Policy + .Handle().Retry(1); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_results_before_exceptions() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .Retry(2); + + var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_handle_both_exception_and_specified_result_if_raised_same_number_of_times_as_retry_count__when_configuring_exception_before_result() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Retry(2); + + var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_exceptions_first() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Retry(4); + + var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_handle_both_exceptions_and_specified_results_if_raised_same_number_of_times_as_retry_count__mixing_exceptions_and_results_specifying_results_first() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Retry(4); + + var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Retry(3); + + var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } + + [Fact] + public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_results_first() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Or() + .Retry(3); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_return_handled_result_when_handled_result_returned_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); + + var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } + + [Fact] + public void Should_throw_when_exception_thrown_next_after_retries_exhaust_handling_both_exceptions_and_specified_results__mixing_exceptions_and_results_specifying_exceptions_first() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Or() + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.FaultAgain, new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_return_unhandled_result_if_not_one_of_results_or_exceptions_specified() + { + Policy policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Or() + .Retry(2); + + var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain); + result.Should().Be(ResultPrimitive.FaultAgain); + } + + [Fact] + public void Should_throw_if_not_one_of_results_or_exceptions_handled() + { + Policy policy = Policy + .Handle() + .OrResult(ResultPrimitive.Fault) + .Retry(2); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ArgumentException(), ResultPrimitive.Good)) + .Should().Throw(); + } + + [Fact] + public void Should_handle_both_exceptions_and_specified_results_with_predicates() + { + Policy policy = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(2); + + var result = policy.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message","key"), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_if_exception_predicate_not_matched() + { + Policy policy = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(2); + + policy.Invoking(p => p.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message", "value"), new ResultClass(ResultPrimitive.Good))) + .Should().Throw(); + } + + [Fact] + public void Should_return_unhandled_result_if_result_predicate_not_matched() + { + Policy policy = Policy + .Handle(e => e.ParamName == "key") + .OrResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(2); + + var result = policy.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"), new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Retry/RetryTResultSpecs.cs b/src/Polly.Specs/Retry/RetryTResultSpecs.cs index 695f961efe5..3712bdb9207 100644 --- a/src/Polly.Specs/Retry/RetryTResultSpecs.cs +++ b/src/Polly.Specs/Retry/RetryTResultSpecs.cs @@ -9,739 +9,740 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensions.ResultAndOrCancellationScenario; -namespace Polly.Specs.Retry; - -public class RetryTResultSpecs +namespace Polly.Specs.Retry { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() + public class RetryTResultSpecs { - Action, int> onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action, int> onRetry = (_, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action, int> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action, int> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action, int, Context> onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action, int, Context> onRetry = (_, _, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action, int, Context> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action, int, Context> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); + [Fact] + public void Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + [Fact] + public void Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. - } + var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. + } - [Fact] - public void Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(3); + [Fact] + public void Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public void Should_return_result_when_result_is_not_the_specified_handled_result() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(); + [Fact] + public void Should_return_result_when_result_is_not_the_specified_handled_result() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(); - var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public void Should_return_result_when_result_is_not_one_of_the_specified_handled_results() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .Retry(); + [Fact] + public void Should_return_result_when_result_is_not_one_of_the_specified_handled_results() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .Retry(); - var result = policy.RaiseResultSequence(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultYetAgain); - } + var result = policy.RaiseResultSequence(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public void Should_return_result_when_specified_result_predicate_is_not_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(); + [Fact] + public void Should_return_result_when_specified_result_predicate_is_not_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); - } + var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public void Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .Retry(); + [Fact] + public void Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); - } + var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public void Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(); + [Fact] + public void Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() - { - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .Retry(); + [Fact] + public void Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() + { + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_handled_result() - { - var expectedFaults = new [] { "Fault #1", "Fault #2", "Fault #3" }; - var retryFaults = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_handled_result() + { + var expectedFaults = new [] { "Fault #1", "Fault #2", "Fault #3" }; + var retryFaults = new List(); - Policy policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .Retry(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); + Policy policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .Retry(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); - IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); - resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); + IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); + resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); - policy.RaiseResultSequence(resultsToRaise); + policy.RaiseResultSequence(resultsToRaise); - retryFaults - .Should() - .ContainInOrder(expectedFaults); - } + retryFaults + .Should() + .ContainInOrder(expectedFaults); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, retryCount) => retryCounts.Add(retryCount)); - policy.RaiseResultSequence(ResultPrimitive.Good); + policy.RaiseResultSequence(ResultPrimitive.Good); - retryCounts.Should() - .BeEmpty(); - } + retryCounts.Should() + .BeEmpty(); + } - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextData = context); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextData = context); - policy.RaiseResultSequence( + policy.RaiseResultSequence( new { key1 = "value1", key2 = "value2" }.AsDictionary(), ResultPrimitive.Fault, ResultPrimitive.Good - ) - .Should().Be(ResultPrimitive.Good); + ) + .Should().Be(ResultPrimitive.Good); - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; - - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextData = context); - - var result = policy.RaiseResultSequenceOnExecuteAndCapture( - new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(ResultPrimitive), - Result = ResultPrimitive.Good - }); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + [Fact] + public void Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextData = context); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => capturedContext = context); + var result = policy.RaiseResultSequenceOnExecuteAndCapture( + new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); + + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(ResultPrimitive), + Result = ResultPrimitive.Good + }); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - capturedContext.Should() - .BeEmpty(); - } + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => capturedContext = context); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextValue = context["key"].ToString()); + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - policy.RaiseResultSequence( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + capturedContext.Should() + .BeEmpty(); + } - contextValue.Should().Be("original_value"); + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - policy.RaiseResultSequence( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextValue = context["key"].ToString()); - contextValue.Should().Be("new_value"); - } + policy.RaiseResultSequence( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - [Fact] - public void Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + contextValue.Should().Be("original_value"); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry((_, _, context) => contextValue = context["key"].ToString()); + policy.RaiseResultSequence( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - policy.RaiseResultSequenceOnExecuteAndCapture( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + contextValue.Should().Be("new_value"); + } - contextValue.Should().Be("original_value"); + [Fact] + public void Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - policy.RaiseResultSequenceOnExecuteAndCapture( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry((_, _, context) => contextValue = context["key"].ToString()); - contextValue.Should().Be("new_value"); - } + policy.RaiseResultSequenceOnExecuteAndCapture( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(1); + contextValue.Should().Be("original_value"); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); + policy.RaiseResultSequenceOnExecuteAndCapture( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); + contextValue.Should().Be("new_value"); + } - } + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(1); - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - var retryInvoked = false; + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); - Action, int> onRetry = (_, _) => { retryInvoked = true; }; + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Good); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(0, onRetry); + } - policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_without_context() + { + var retryInvoked = false; - retryInvoked.Should().BeFalse(); - } + Action, int> onRetry = (_, _) => { retryInvoked = true; }; - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_context() - { - var retryInvoked = false; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(0, onRetry); - Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; + policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(0, onRetry); + retryInvoked.Should().BeFalse(); + } - policy.RaiseResultSequence( - new { key = "value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_context() + { + var retryInvoked = false; - retryInvoked.Should().BeFalse(); - } + Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; - #region Sync cancellation tests + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(0, onRetry); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + policy.RaiseResultSequence( + new { key = "value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good).Should().Be(ResultPrimitive.Fault); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + retryInvoked.Should().BeFalse(); + } - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Sync cancellation tests - var scenario = new Scenario + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Good); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Good); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } - - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(2); - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); - - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(2); + } + + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; - - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - attemptsInvoked.Should().Be(2); - } - - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(2); + } + + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; + + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1 + 3); + } + + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; + + policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good) + .Should().Be(ResultPrimitive.Fault); + + attemptsInvoked.Should().Be(1 + 3); + } + + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; - - policy.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good) - .Should().Be(ResultPrimitive.Fault); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .Retry(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .Retry(3, (_, _) => + var scenario = new Scenario { - cancellationTokenSource.Cancel(); - }); + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var scenario = new Scenario - { - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + attemptsInvoked.Should().Be(1); + } - policy.Invoking(x => x.RaiseResultSequenceAndOrCancellation(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + #endregion - attemptsInvoked.Should().Be(1); } - - #endregion - } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs index c4a1ce83017..e9d0b35d41d 100644 --- a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs +++ b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs @@ -10,751 +10,752 @@ using Scenario = Polly.Specs.Helpers.PolicyTResultExtensionsAsync.ResultAndOrCancellationScenario; -namespace Polly.Specs.Retry; - -public class RetryTResultSpecsAsync +namespace Polly.Specs.Retry { - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() + public class RetryTResultSpecsAsync { - Action, int> onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action, int> onRetry = (_, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_without_context_is_null() - { - Action, int> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_without_context_is_null() + { + Action, int> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action, int, Context> onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action, int, Context> onRetry = (_, _, _) => { }; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(-1, onRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(-1, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_onretry_action_with_context_is_null() - { - Action, int, Context> nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_with_context_is_null() + { + Action, int, Context> nullOnRetry = null; - Action policy = () => Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(1, nullOnRetry); + Action policy = () => Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(1, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public async Task Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(3); + [Fact] + public async Task Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Good); - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + [Fact] + public async Task Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. + } - [Fact] - public async Task Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(3); + [Fact] + public async Task Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public async Task Should_return_result_when_result_is_not_the_specified_handled_result() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_result_is_not_the_specified_handled_result() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultAgain); - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public async Task Should_return_result_when_result_is_not_one_of_the_specified_handled_results() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_result_is_not_one_of_the_specified_handled_results() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); - result.Should().Be(ResultPrimitive.FaultYetAgain); - } + var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); + result.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public async Task Should_return_result_when_specified_result_predicate_is_not_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_specified_result_predicate_is_not_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); - } + var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); + } - [Fact] - public async Task Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .RetryAsync(); + [Fact] + public async Task Should_return_result_when_none_of_the_specified_result_predicates_are_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); - } + var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); + } - [Fact] - public async Task Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .RetryAsync(); + [Fact] + public async Task Should_not_return_handled_result_when_specified_result_predicate_is_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() - { - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) - .RetryAsync(); + [Fact] + public async Task Should_not_return_handled_result_when_one_of_the_specified_result_predicates_is_satisfied() + { + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) + .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); - result.ResultCode.Should().Be(ResultPrimitive.Good); - } + var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + result.ResultCode.Should().Be(ResultPrimitive.Good); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3, (_, retryCount) => retryCounts.Add(retryCount)); - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_handled_result() - { - var expectedFaults = new[] { "Fault #1", "Fault #2", "Fault #3" }; - var retryFaults = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_handled_result() + { + var expectedFaults = new[] { "Fault #1", "Fault #2", "Fault #3" }; + var retryFaults = new List(); - var policy = Policy - .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) - .RetryAsync(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); + var policy = Policy + .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) + .RetryAsync(3, (outcome, _) => retryFaults.Add(outcome.Result.SomeString)); - IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); - resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); + IList resultsToRaise = expectedFaults.Select(s => new ResultClass(ResultPrimitive.Fault, s)).ToList(); + resultsToRaise.Add(new ResultClass(ResultPrimitive.Fault)); - (await policy.RaiseResultSequenceAsync(resultsToRaise)) - .ResultCode.Should().Be(ResultPrimitive.Fault); + (await policy.RaiseResultSequenceAsync(resultsToRaise)) + .ResultCode.Should().Be(ResultPrimitive.Fault); - retryFaults - .Should() - .ContainInOrder(expectedFaults); - } + retryFaults + .Should() + .ContainInOrder(expectedFaults); + } - [Fact] - public async Task Should_not_call_onretry_when_no_retries_are_performed() - { - var retryCounts = new List(); + [Fact] + public async Task Should_not_call_onretry_when_no_retries_are_performed() + { + var retryCounts = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, retryCount) => retryCounts.Add(retryCount)); - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - retryCounts.Should() - .BeEmpty(); - } + retryCounts.Should() + .BeEmpty(); + } - [Fact] - public async Task Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public async Task Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextData = context); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextData = context); - (await policy.RaiseResultSequenceAsync( + (await policy.RaiseResultSequenceAsync( new { key1 = "value1", key2 = "value2" }.AsDictionary(), ResultPrimitive.Fault, ResultPrimitive.Good - )) - .Should().Be(ResultPrimitive.Good); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } - - [Fact] - public async Task Should_call_onretry_with_the_passed_context_when_execute_and_capture() - { - IDictionary contextData = null; - - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextData = context); - - var result = await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); - - result.Should().BeEquivalentTo(new - { - Outcome = OutcomeType.Successful, - FinalException = (Exception)null, - ExceptionType = (ExceptionType?)null, - FaultType = (FaultType?)null, - FinalHandledResult = default(ResultPrimitive), - Result = ResultPrimitive.Good - }); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } - - [Fact] - public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() - { - Context capturedContext = null; - - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => capturedContext = context); - - await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - - capturedContext.Should() - .BeEmpty(); - } - - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; - - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - - await policy.RaiseResultSequenceAsync( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + )) + .Should().Be(ResultPrimitive.Good); - contextValue.Should().Be("original_value"); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - await policy.RaiseResultSequenceAsync( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); - - contextValue.Should().Be("new_value"); - } - - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute_and_capture() - { - string contextValue = null; + [Fact] + public async Task Should_call_onretry_with_the_passed_context_when_execute_and_capture() + { + IDictionary contextData = null; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextData = context); - await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( - new { key = "original_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + var result = await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - contextValue.Should().Be("original_value"); + result.Should().BeEquivalentTo(new + { + Outcome = OutcomeType.Successful, + FinalException = (Exception)null, + ExceptionType = (ExceptionType?)null, + FaultType = (FaultType?)null, + FinalHandledResult = default(ResultPrimitive), + Result = ResultPrimitive.Good + }); - await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( - new { key = "new_value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good - ); + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - contextValue.Should().Be("new_value"); - } + [Fact] + public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() + { + Context capturedContext = null; - [Fact] - public async Task Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(1); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => capturedContext = context); - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); + await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); + capturedContext.Should() + .BeEmpty(); + } - } + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - [Fact] - public async Task Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - var retryInvoked = false; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - Action, int> onRetry = (_, _) => { retryInvoked = true; }; + await policy.RaiseResultSequenceAsync( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(0, onRetry); + contextValue.Should().Be("original_value"); - (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); + await policy.RaiseResultSequenceAsync( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - retryInvoked.Should().BeFalse(); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public async Task Should_not_call_onretry_when_retry_count_is_zero_with_context() - { - var retryInvoked = false; + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute_and_capture() + { + string contextValue = null; - Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync((_, _, context) => contextValue = context["key"].ToString()); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(0, onRetry); + await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( + new { key = "original_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - (await policy.RaiseResultSequenceAsync( - new { key = "value" }.AsDictionary(), - ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); + contextValue.Should().Be("original_value"); - retryInvoked.Should().BeFalse(); - } + await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( + new { key = "new_value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good + ); - #region Async and cancellation tests + contextValue.Should().Be("new_value"); + } - [Fact] - public async Task Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + [Fact] + public async Task Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(1); - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Good); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(async (_, _) => - { - await Task.Delay(shimTimeSpan); - executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; - }); + } - (await policy.ExecuteAsync(async () => + [Fact] + public async Task Should_not_call_onretry_when_retry_count_is_zero_without_context() { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - return ResultPrimitive.Fault; - })).Should().Be(ResultPrimitive.Fault); - - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + var retryInvoked = false; - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + Action, int> onRetry = (_, _) => { retryInvoked = true; }; - [Fact] - public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(0, onRetry); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + (await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + retryInvoked.Should().BeFalse(); + } - var scenario = new Scenario + [Fact] + public async Task Should_not_call_onretry_when_retry_count_is_zero_with_context() { - AttemptDuringWhichToCancel = null, - }; + var retryInvoked = false; - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Good); + Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; - attemptsInvoked.Should().Be(1 + 3); - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(0, onRetry); - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + (await policy.RaiseResultSequenceAsync( + new { key = "value" }.AsDictionary(), + ResultPrimitive.Fault, ResultPrimitive.Good)).Should().Be(ResultPrimitive.Fault); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + retryInvoked.Should().BeFalse(); + } - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Async and cancellation tests - var scenario = new Scenario + [Fact] + public async Task Should_wait_asynchronously_for_async_onretry_delegate() { - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - cancellationTokenSource.Cancel(); + var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var executeDelegateInvocations = 0; + var executeDelegateInvocationsWhenOnRetryExits = 0; - attemptsInvoked.Should().Be(0); - } + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(async (_, _) => + { + await Task.Delay(shimTimeSpan); + executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; + }); - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + (await policy.ExecuteAsync(async () => + { + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + return ResultPrimitive.Fault; + })).Should().Be(ResultPrimitive.Fault); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Good); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; - - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } - - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); - - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var scenario = new Scenario - { - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(1); - } + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; + + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; + + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } + + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; + + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(2); + } + + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() - { - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3); + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; + + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(2); + } + + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() + { + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + var scenario = new Scenario + { + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; + + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1 + 3); + } + + [Fact] + public async Task Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; - - (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Be(ResultPrimitive.Fault); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3); - attemptsInvoked.Should().Be(1 + 3); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .RetryAsync(3, (_, _) => + var scenario = new Scenario { - cancellationTokenSource.Cancel(); - }); - - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; - - var scenario = new Scenario + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; + + (await policy.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Be(ResultPrimitive.Fault); + + attemptsInvoked.Should().Be(1 + 3); + } + + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Fault, - ResultPrimitive.Good)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .RetryAsync(3, (_, _) => + { + cancellationTokenSource.Cancel(); + }); - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; + + var scenario = new Scenario + { + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; + + policy.Awaiting(x => x.RaiseResultSequenceAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Fault, + ResultPrimitive.Good)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + + attemptsInvoked.Should().Be(1); + } - #endregion -} \ No newline at end of file + #endregion + } +} diff --git a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs index 1f8fa3d0ca6..8d29426202b 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs @@ -13,626 +13,626 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetryAsyncSpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetryAsyncSpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetryAsyncSpecs : IDisposable { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } - - [Fact] - public void Should_throw_when_sleep_durations_is_null_without_context() - { - Action onRetry = (_, _) => { }; - - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(null, onRetry); + public WaitAndRetryAsyncSpecs() + { + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } + [Fact] + public void Should_throw_when_sleep_durations_is_null_without_context() + { + Action onRetry = (_, _) => { }; - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(null, onRetry); - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty(), nullOnRetry); + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; - [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty(), nullOnRetry); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var totalTimeSlept = 0; + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - SystemClock.SleepAsync = (span, _) => + [Fact] + public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; - }; + var totalTimeSlept = 0; - await policy.RaiseExceptionAsync(3); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + SystemClock.SleepAsync = (span, _) => + { + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; - [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() - { - var totalTimeSlept = 0; + await policy.RaiseExceptionAsync(3); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - SystemClock.SleepAsync = (span, _) => + [Fact] + public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; - }; + var totalTimeSlept = 0; - policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) - .Should().Throw(); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + SystemClock.SleepAsync = (span, _) => + { + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; - [Fact] - public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var totalTimeSlept = 0; + policy.Awaiting(x => x.RaiseExceptionAsync(3 + 1)) + .Should().Throw(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - SystemClock.SleepAsync = (span, _) => + [Fact] + public async Task Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; - }; + var totalTimeSlept = 0; - await policy.RaiseExceptionAsync(2); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - totalTimeSlept.Should() - .Be(1 + 2); - } + SystemClock.SleepAsync = (span, _) => + { + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; - [Fact] - public void Should_not_sleep_if_no_retries() - { - var totalTimeSlept = 0; + await policy.RaiseExceptionAsync(2); - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty()); + totalTimeSlept.Should() + .Be(1 + 2); + } - SystemClock.SleepAsync = (span, _) => + [Fact] + public void Should_not_sleep_if_no_retries() { - totalTimeSlept += span.Seconds; - return TaskHelper.EmptyTask; - }; + var totalTimeSlept = 0; - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty()); - totalTimeSlept.Should() - .Be(0); - } + SystemClock.SleepAsync = (span, _) => + { + totalTimeSlept += span.Seconds; + return TaskHelper.EmptyTask; + }; - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_timespan() - { - var expectedRetryWaits = new [] + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + + totalTimeSlept.Should() + .Be(0); + } + + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_timespan() { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }; + var expectedRetryWaits = new [] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); - await policy.RaiseExceptionAsync(3); + await policy.RaiseExceptionAsync(3); - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (exception, _) => retryExceptions.Add(exception)); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); - await policy.RaiseExceptionAsync(3); + await policy.RaiseExceptionAsync(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - retryExceptions.Should().BeEmpty(); - } + retryExceptions.Should().BeEmpty(); + } - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public async Task Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public async Task Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, context) => contextData = context); - - await policy.RaiseExceptionAsync( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, context) => contextData = context); + + await policy.RaiseExceptionAsync( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Context_should_be_empty_if_execute_not_called_with_any_data() - { - Context capturedContext = null; + [Fact] + public void Context_should_be_empty_if_execute_not_called_with_any_data() + { + Context capturedContext = null; - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, context) => capturedContext = context); - policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); - - capturedContext.Should() - .BeEmpty(); - } + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, context) => capturedContext = context); + policy.Awaiting(x => x.RaiseExceptionAsync()).Should().NotThrow(); + + capturedContext.Should() + .BeEmpty(); + } - [Fact] - public async Task Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + [Fact] + public async Task Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds() }, (_, _, context) => contextValue = context["key"].ToString()); - await policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); + await policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - await policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + await policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() + ); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(-1, _ => TimeSpan.Zero, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(-1, _ => TimeSpan.Zero, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(1, null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(1, null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetryAsync(1, _ => TimeSpan.Zero, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryAsync(1, _ => TimeSpan.Zero, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] + [Fact] + public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetryAsync(5, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) - ); + var expectedRetryWaits = new[] + { + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryAsync(5, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); + + await policy.RaiseExceptionAsync(5); + + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - await policy.RaiseExceptionAsync(5); + [Fact] + public async Task Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() + { + object capturedExceptionInstance = null; - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + var exceptionInstance = new DivideByZeroException(); - [Fact] - public async Task Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() - { - object capturedExceptionInstance = null; + var policy = Policy + .Handle() + .WaitAndRetryAsync(5, + sleepDurationProvider:( _, ex, _) => + { + capturedExceptionInstance = ex; + return TimeSpan.FromMilliseconds(0); + }, + onRetryAsync: (_, _, _, _) => TaskHelper.EmptyTask); - var exceptionInstance = new DivideByZeroException(); + await policy.RaiseExceptionAsync(exceptionInstance); - var policy = Policy - .Handle() - .WaitAndRetryAsync(5, - sleepDurationProvider:( _, ex, _) => - { - capturedExceptionInstance = ex; - return TimeSpan.FromMilliseconds(0); - }, - onRetryAsync: (_, _, _, _) => TaskHelper.EmptyTask); - - await policy.RaiseExceptionAsync(exceptionInstance); + capturedExceptionInstance.Should().Be(exceptionInstance); + } - capturedExceptionInstance.Should().Be(exceptionInstance); - } + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, - }; + var actualRetryWaits = new List(); - var actualRetryWaits = new List(); + var policy = Policy + .Handle() + .WaitAndRetryAsync(2, + sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], + onRetryAsync: (_, timeSpan, _, _) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); - var policy = Policy - .Handle() - .WaitAndRetryAsync(2, - sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], - onRetryAsync: (_, timeSpan, _, _) => - { - actualRetryWaits.Add(timeSpan); + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(() => { + if (enumerator.MoveNext()) throw enumerator.Current.Key; return TaskHelper.EmptyTask; }); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(() => { - if (enumerator.MoveNext()) throw enumerator.Current.Key; - return TaskHelper.EmptyTask; - }); + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } - - [Fact] - public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() - { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; + [Fact] + public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; - var defaultRetryAfter = 30.Seconds(); + var defaultRetryAfter = 30.Seconds(); - var policy = Policy - .Handle() - .WaitAndRetryAsync(1, - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + var policy = Policy + .Handle() + .WaitAndRetryAsync(1, + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); - var failedOnce = false; - await policy.ExecuteAsync(async (context, _) => + var failedOnce = false; + await policy.ExecuteAsync(async (context, _) => { await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. context["RetryAfter"] = expectedRetryDuration; @@ -643,46 +643,46 @@ await policy.ExecuteAsync(async (context, _) => throw new DivideByZeroException(); } }, - new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. - CancellationToken.None - ); + new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. + CancellationToken.None + ); - actualRetryDuration.Should().Be(expectedRetryDuration); - } + actualRetryDuration.Should().Be(expectedRetryDuration); + } - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero() - { - var retryInvoked = false; + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero() + { + var retryInvoked = false; - Action onRetry = (_, _) => { retryInvoked = true; }; + Action onRetry = (_, _) => { retryInvoked = true; }; - var policy = Policy - .Handle() - .WaitAndRetryAsync(0, _ => TimeSpan.FromSeconds(1), onRetry); + var policy = Policy + .Handle() + .WaitAndRetryAsync(0, _ => TimeSpan.FromSeconds(1), onRetry); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - retryInvoked.Should().BeFalse(); - } + retryInvoked.Should().BeFalse(); + } - [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + [Fact] + public void Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + var executeDelegateInvocations = 0; + var executeDelegateInvocationsWhenOnRetryExits = 0; - var policy = Policy - .Handle() - .WaitAndRetryAsync(1, + var policy = Policy + .Handle() + .WaitAndRetryAsync(1, _ => TimeSpan.Zero, async (_, _) => { @@ -690,420 +690,421 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; }); - policy.Awaiting(p => p.ExecuteAsync(async () => - { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - throw new DivideByZeroException(); - })).Should().Throw(); - - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } - - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + policy.Awaiting(p => p.ExecuteAsync(async () => + { + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + throw new DivideByZeroException(); + })).Should().Throw(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new Scenario + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - SystemClock.SleepAsync = Task.Delay; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; + attemptsInvoked.Should().Be(1 + 3); + } + + [Fact] + public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() + { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { retryDelay }); + SystemClock.SleepAsync = Task.Delay; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; - var watch = new Stopwatch(); - watch.Start(); + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { retryDelay }); - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 1, - AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. - ActionObservesCancellation = false - }; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - cancellationTokenSource.CancelAfter(shimTimeSpan); + var watch = new Stopwatch(); + watch.Start(); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - watch.Stop(); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 1, + AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. + ActionObservesCancellation = false + }; - attemptsInvoked.Should().Be(1); + cancellationTokenSource.CancelAfter(shimTimeSpan); - watch.Elapsed.Should().BeLessThan(retryDelay); - watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: (int)(shimTimeSpan.TotalMilliseconds) / 2); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. - } + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + watch.Stop(); - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + attemptsInvoked.Should().Be(1); + + watch.Elapsed.Should().BeLessThan(retryDelay); + watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: (int)(shimTimeSpan.TotalMilliseconds) / 2); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. + } - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; + + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, (_, _) => { cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().NotThrow(); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs index ec1f60caada..0d60d20272e 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs @@ -12,350 +12,350 @@ using Scenario = Polly.Specs.Helpers.PolicyExtensionsAsync.ExceptionAndOrCancellationScenario; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetryForeverAsyncSpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetryForeverAsyncSpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetryForeverAsyncSpecs : IDisposable { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } + public WaitAndRetryForeverAsyncSpecs() + { + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; + Func provider = _ => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context() - { - Action nullOnRetry = null; - Func provider = (_, _) => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context() + { + Action nullOnRetry = null; + Func provider = (_, _) => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForeverAsync(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForeverAsync(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForeverAsync(_ => TimeSpan.Zero); - policy.Awaiting(x => x.RaiseExceptionAsync(3)) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle(_ => false) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => false) + .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - Func provider = _ => 1.Seconds(); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + Func provider = _ => 1.Seconds(); - var policy = Policy - .Handle(_ => true) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => true) + .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => 1.Seconds(); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => 1.Seconds(); - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_sleep_if_no_retries() - { - Func provider = _ => 1.Seconds(); + [Fact] + public void Should_not_sleep_if_no_retries() + { + Func provider = _ => 1.Seconds(); - var totalTimeSlept = 0; + var totalTimeSlept = 0; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - totalTimeSlept.Should() - .Be(0); - } + totalTimeSlept.Should() + .Be(0); + } - [Fact] - public async Task Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); - Func provider = _ => TimeSpan.Zero; + [Fact] + public async Task Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); - await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); + await policy.RaiseExceptionAsync(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); - policy.RaiseExceptionAsync(3); + policy.RaiseExceptionAsync(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - Func provider = _ => 1.Seconds(); - var retryExceptions = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + Func provider = _ => 1.Seconds(); + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (exception, _) => retryExceptions.Add(exception)); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); - retryExceptions.Should().BeEmpty(); - } + retryExceptions.Should().BeEmpty(); + } - [Fact] - public void Should_create_new_context_for_each_call_to_policy() - { - Func provider = (_, _) => 1.Seconds(); + [Fact] + public void Should_create_new_context_for_each_call_to_policy() + { + Func provider = (_, _) => 1.Seconds(); - string contextValue = null; + string contextValue = null; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( provider, (_, _, context) => contextValue = context["key"].ToString()); - policy.RaiseExceptionAsync( - new { key = "original_value" }.AsDictionary() - ); - - contextValue.Should().Be("original_value"); + policy.RaiseExceptionAsync( + new { key = "original_value" }.AsDictionary() + ); - policy.RaiseExceptionAsync( - new { key = "new_value" }.AsDictionary() - ); + contextValue.Should().Be("original_value"); - contextValue.Should().Be("new_value"); - } - - [Fact] - public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] - { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) + policy.RaiseExceptionAsync( + new { key = "new_value" }.AsDictionary() ); - await policy.RaiseExceptionAsync(5); + contextValue.Should().Be("new_value"); + } - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + [Fact] + public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() + { + var expectedRetryWaits = new[] + { + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); + + await policy.RaiseExceptionAsync(5); + + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, - }; + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], - onRetryAsync: (_, timeSpan, _) => - { - actualRetryWaits.Add(timeSpan); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + sleepDurationProvider: (_, exc, _) => expectedRetryWaits[exc], + onRetryAsync: (_, timeSpan, _) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(() => { + if (enumerator.MoveNext()) throw enumerator.Current.Key; return TaskHelper.EmptyTask; }); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(() => { - if (enumerator.MoveNext()) throw enumerator.Current.Key; - return TaskHelper.EmptyTask; - }); + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } - - [Fact] - public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() - { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; + [Fact] + public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; - var defaultRetryAfter = 30.Seconds(); + var defaultRetryAfter = 30.Seconds(); - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); - var failedOnce = false; - await policy.ExecuteAsync(async (context, _) => + var failedOnce = false; + await policy.ExecuteAsync(async (context, _) => { await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. context["RetryAfter"] = expectedRetryDuration; @@ -366,29 +366,29 @@ await policy.ExecuteAsync(async (context, _) => throw new DivideByZeroException(); } }, - new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. - CancellationToken.None - ); + new { RetryAfter = defaultRetryAfter }.AsDictionary(), // Can also set an initial value for RetryAfter, in the Context passed into the call. + CancellationToken.None + ); - actualRetryDuration.Should().Be(expectedRetryDuration); - } + actualRetryDuration.Should().Be(expectedRetryDuration); + } - [Fact] - public void Should_wait_asynchronously_for_async_onretry_delegate() - { - // This test relates to https://github.com/App-vNext/Polly/issues/107. - // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx - // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. - // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. + [Fact] + public void Should_wait_asynchronously_for_async_onretry_delegate() + { + // This test relates to https://github.com/App-vNext/Polly/issues/107. + // An async (...) => { ... } anonymous delegate with no return type may compile to either an async void or an async Task method; which assign to an Action<...> or Func<..., Task> respectively. However, if it compiles to async void (assigning tp Action<...>), then the delegate, when run, will return at the first await, and execution continues without waiting for the Action to complete, as described by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx + // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. + // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + var executeDelegateInvocations = 0; + var executeDelegateInvocationsWhenOnRetryExits = 0; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync( + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( _ => TimeSpan.Zero, async (_, _) => { @@ -396,318 +396,319 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() executeDelegateInvocationsWhenOnRetryExits = executeDelegateInvocations; }); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => + { + executeDelegateInvocations++; + await TaskHelper.EmptyTask; + if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } + })).Should().NotThrow(); + + while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + + executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. + executeDelegateInvocations.Should().Be(2); + } + + [Fact] + public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - executeDelegateInvocations++; - await TaskHelper.EmptyTask; - if (executeDelegateInvocations == 1) { throw new DivideByZeroException(); } - })).Should().NotThrow(); + Func provider = _ => TimeSpan.Zero; - while (executeDelegateInvocationsWhenOnRetryExits == 0) { } // Wait for the onRetry delegate to complete. + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - executeDelegateInvocationsWhenOnRetryExits.Should().Be(1); // If the async onRetry delegate is genuinely awaited, only one execution of the .Execute delegate should have occurred by the time onRetry completes. If the async onRetry delegate were instead assigned to an Action<...>, then onRetry will return, and the second action execution will commence, before await Task.Delay() completes, leaving executeDelegateInvocationsWhenOnRetryExits == 2. - executeDelegateInvocations.Should().Be(2); - } + var cancellationTokenSource = new CancellationTokenSource(); - [Fact] - public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() - { - Func provider = _ => TimeSpan.Zero; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - var cancellationTokenSource = new CancellationTokenSource(); + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().NotThrow(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + Func provider = _ => TimeSpan.Zero; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().NotThrow(); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - attemptsInvoked.Should().Be(1); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - Func provider = _ => TimeSpan.Zero; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + cancellationTokenSource.Cancel(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var scenario = new Scenario + attemptsInvoked.Should().Be(0); + } + + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + Func provider = _ => TimeSpan.Zero; - cancellationTokenSource.Cancel(); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(0); - } - - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - Func provider = _ => TimeSpan.Zero; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + Func provider = _ => TimeSpan.Zero; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - attemptsInvoked.Should().Be(1); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - Func provider = _ => TimeSpan.Zero; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + Func provider = _ => TimeSpan.Zero; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - attemptsInvoked.Should().Be(1); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - Func provider = _ => TimeSpan.Zero; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + Func provider = _ => TimeSpan.Zero; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - attemptsInvoked.Should().Be(1); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - Func provider = _ => TimeSpan.Zero; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + Func provider = _ => TimeSpan.Zero; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - attemptsInvoked.Should().Be(2); - } + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - Func provider = _ => TimeSpan.Zero; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new Scenario + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; - - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - - attemptsInvoked.Should().Be(2); - } - - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - Func provider = _ => TimeSpan.Zero; + Func provider = _ => TimeSpan.Zero; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider, + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (_, _) => { cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + policy.Awaiting(x => x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); + var cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null, - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null, + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action).Should().NotThrow(); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action).Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForeverAsync(provider); + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new Scenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new Scenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); - policy.Awaiting(action) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + Func action = async x => result = await x.RaiseExceptionAndOrCancellationAsync(scenario, cancellationTokenSource, onExecute, true); + policy.Awaiting(action) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs index 30daa2e2f2b..7ba4bd39c57 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs @@ -7,363 +7,364 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetryForeverSpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetryForeverSpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetryForeverSpecs : IDisposable { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } + public WaitAndRetryForeverSpecs() + { + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - Func sleepDurationProvider = null; + Func sleepDurationProvider = null; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(sleepDurationProvider, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(sleepDurationProvider, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; + Func provider = _ => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context() - { - Action nullOnRetry = null; - Func provider = (_, _) => TimeSpan.Zero; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context() + { + Action nullOnRetry = null; + Func provider = (_, _) => TimeSpan.Zero; - Action policy = () => Policy - .Handle() - .WaitAndRetryForever(provider, nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetryForever(provider, nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .WaitAndRetryForever(_ => new TimeSpan()); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .WaitAndRetryForever(_ => new TimeSpan()); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForever(_ => new TimeSpan()); + [Fact] + public void Should_not_throw_regardless_of_how_many_times_one_of_the_specified_exception_is_raised() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForever(_ => new TimeSpan()); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForever(provider); + var policy = Policy + .Handle() + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .Or() - .WaitAndRetryForever(provider); + var policy = Policy + .Handle() + .Or() + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle(_ => false) - .WaitAndRetryForever(provider); + var policy = Policy + .Handle(_ => false) + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetryForever(provider); + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - Func provider = _ => 1.Seconds(); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + Func provider = _ => 1.Seconds(); - var policy = Policy - .Handle(_ => true) - .WaitAndRetryForever(provider); + var policy = Policy + .Handle(_ => true) + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - Func provider = _ => 1.Seconds(); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + Func provider = _ => 1.Seconds(); - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryForever(provider); + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryForever(provider); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_sleep_if_no_retries() - { - Func provider = _ => 1.Seconds(); + [Fact] + public void Should_not_sleep_if_no_retries() + { + Func provider = _ => 1.Seconds(); - var totalTimeSlept = 0; + var totalTimeSlept = 0; - var policy = Policy - .Handle() - .WaitAndRetryForever(provider); + var policy = Policy + .Handle() + .WaitAndRetryForever(provider); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - totalTimeSlept.Should() - .Be(0); - } + totalTimeSlept.Should() + .Be(0); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); - Func provider = _ => TimeSpan.Zero; + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + Func provider = _ => TimeSpan.Zero; - var policy = Policy - .Handle() - .WaitAndRetryForever(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (_, retryCount, _) => retryCounts.Add(retryCount)); - policy.RaiseException(3); + policy.RaiseException(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - Func provider = _ => 1.Seconds(); - var retryExceptions = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + Func provider = _ => 1.Seconds(); + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (exception, _) => retryExceptions.Add(exception)); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - retryExceptions.Should().BeEmpty(); - } + retryExceptions.Should().BeEmpty(); + } - [Fact] - public void Should_create_new_context_for_each_call_to_policy() - { - Func provider = (_, _) => 1.Seconds(); + [Fact] + public void Should_create_new_context_for_each_call_to_policy() + { + Func provider = (_, _) => 1.Seconds(); - string contextValue = null; + string contextValue = null; - var policy = Policy - .Handle() - .WaitAndRetryForever( + var policy = Policy + .Handle() + .WaitAndRetryForever( provider, (_, _, context) => contextValue = context["key"].ToString()); - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); - contextValue.Should().Be("original_value"); + contextValue.Should().Be("original_value"); - policy.RaiseException( - new { key = "new_value" }.AsDictionary() - ); + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); - contextValue.Should().Be("new_value"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] + [Fact] + public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetryForever( - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) - ); - - policy.RaiseException(5); + var expectedRetryWaits = new[] + { + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryForever( + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); + + policy.RaiseException(5); + + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, - }; + var actualRetryWaits = new List(); - var actualRetryWaits = new List(); + var policy = Policy + .Handle() + .WaitAndRetryForever( + (_, exc, _) => expectedRetryWaits[exc], + (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) + ); - var policy = Policy - .Handle() - .WaitAndRetryForever( - (_, exc, _) => expectedRetryWaits[exc], - (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) - ); + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } - - [Fact] - public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() - { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; - - var defaultRetryAfter = 30.Seconds(); + [Fact] + public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; - var policy = Policy - .Handle() - .WaitAndRetryForever( - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan) context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + var defaultRetryAfter = 30.Seconds(); - var failedOnce = false; - policy.Execute(context => - { - // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. - context["RetryAfter"] = expectedRetryDuration; + var policy = Policy + .Handle() + .WaitAndRetryForever( + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan) context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); - if (!failedOnce) + var failedOnce = false; + policy.Execute(context => { - failedOnce = true; - throw new DivideByZeroException(); - } - }, - new {RetryAfter = defaultRetryAfter}.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. - ); - - actualRetryDuration.Should().Be(expectedRetryDuration); - } + // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. + context["RetryAfter"] = expectedRetryDuration; + + if (!failedOnce) + { + failedOnce = true; + throw new DivideByZeroException(); + } + }, + new {RetryAfter = defaultRetryAfter}.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. + ); + + actualRetryDuration.Should().Be(expectedRetryDuration); + } - public void Dispose() - { - SystemClock.Reset(); + public void Dispose() + { + SystemClock.Reset(); + } } } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs index bf02728026f..9bf5d284b13 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs @@ -7,55 +7,56 @@ using Xunit; using FluentAssertions.Extensions; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetryForeverTResultAsyncSpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetryForeverTResultAsyncSpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetryForeverTResultAsyncSpecs : IDisposable { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } - - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ - - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; - - var actualRetryWaits = new List(); + public WaitAndRetryForeverTResultAsyncSpecs() + { + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetryForeverAsync( - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _) => + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryForeverAsync( + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(async () => { - actualRetryWaits.Add(timeSpan); - return TaskHelper.EmptyTask; + await TaskHelper.EmptyTask; + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; }); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; - }); + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } + public void Dispose() + { + SystemClock.Reset(); + } - public void Dispose() - { - SystemClock.Reset(); } - } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs index 79822ef8a73..ce394135e76 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs @@ -6,51 +6,52 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetryForeverTResultSpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetryForeverTResultSpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetryForeverTResultSpecs : IDisposable { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } + public WaitAndRetryForeverTResultSpecs() + { + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetryForever( - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) - ); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryForever( + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _) => actualRetryWaits.Add(timeSpan) + ); - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - policy.Execute(() => + using (var enumerator = expectedRetryWaits.GetEnumerator()) { - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; - }); + policy.Execute(() => + { + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } + public void Dispose() + { + SystemClock.Reset(); + } - public void Dispose() - { - SystemClock.Reset(); } - } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs index d451a1d3a13..922d60e83ab 100644 --- a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs @@ -10,771 +10,771 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetrySpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetrySpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetrySpecs : IDisposable { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } + public WaitAndRetrySpecs() + { + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_throw_when_sleep_durations_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_sleep_durations_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - [Fact] - public void Should_throw_when_sleep_durations_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_durations_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - [Fact] - public void Should_throw_when_sleep_durations_is_null_with_attempts_with_context() - { - Action onRetry = (_, _, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_durations_is_null_with_attempts_with_context() + { + Action onRetry = (_, _, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurations"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurations"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_attempts_with_context() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_attempts_with_context() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(3)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(3)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(2)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(2)) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_then_times_as_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exceptions_thrown_less_number_then_times_as_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(2)) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException(2)) + .Should().NotThrow(); + } - [Fact] - public void Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_throw_when_specified_exception_thrown_more_times_than_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_there_are_sleep_durations() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + [Fact] + public void Should_throw_when_one_of_the_specified_exceptions_are_thrown_more_times_there_are_sleep_durations() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() - { - var policy = Policy - .Handle() - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type() + { + var policy = Policy + .Handle() + .WaitAndRetry(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() - { - var policy = Policy - .Handle() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_the_specified_exception_type_async() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetry(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types_async() - { - var policy = Policy - .Handle() - .Or() - .WaitAndRetryAsync(Enumerable.Empty()); + [Fact] + public void Should_throw_when_exception_thrown_is_not_one_of_the_specified_exception_types_async() + { + var policy = Policy + .Handle() + .Or() + .WaitAndRetryAsync(Enumerable.Empty()); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().Throw(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_specified_exception_predicate_is_not_satisfied() - { - var policy = Policy - .Handle(_ => false) - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public void Should_throw_when_specified_exception_predicate_is_not_satisfied() + { + var policy = Policy + .Handle(_ => false) + .WaitAndRetry(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => false) - .Or(_ => false) - .WaitAndRetry(Enumerable.Empty()); + [Fact] + public void Should_throw_when_none_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => false) + .Or(_ => false) + .WaitAndRetry(Enumerable.Empty()); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); - } + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied() - { - var policy = Policy - .Handle(_ => true) - .WaitAndRetry(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied() + { + var policy = Policy + .Handle(_ => true) + .WaitAndRetry(new[] + { + 1.Seconds() + }); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_specified_exception_predicate_is_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_not_throw_when_specified_exception_predicate_is_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetry(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetry(new[] + { + 1.Seconds() + }); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() - { - var policy = Policy - .Handle(_ => true) - .Or(_ => true) - .WaitAndRetryAsync(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_not_throw_when_one_of_the_specified_exception_predicates_are_satisfied_async() + { + var policy = Policy + .Handle(_ => true) + .Or(_ => true) + .WaitAndRetryAsync(new[] + { + 1.Seconds() + }); - policy.Awaiting(x => x.RaiseExceptionAsync()) - .Should().NotThrow(); - } + policy.Awaiting(x => x.RaiseExceptionAsync()) + .Should().NotThrow(); + } - [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() - { - var totalTimeSlept = 0; + [Fact] + public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations() + { + var totalTimeSlept = 0; - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - policy.RaiseException(3); + policy.RaiseException(3); - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() - { - var totalTimeSlept = 0; + [Fact] + public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_more_number_of_times_than_there_are_sleep_durations() + { + var totalTimeSlept = 0; - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - policy.Invoking(x => x.RaiseException(3 + 1)) - .Should().Throw(); + policy.Invoking(x => x.RaiseException(3 + 1)) + .Should().Throw(); - totalTimeSlept.Should() - .Be(1 + 2 + 3); - } + totalTimeSlept.Should() + .Be(1 + 2 + 3); + } - [Fact] - public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() - { - var totalTimeSlept = 0; + [Fact] + public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_less_number_of_times_than_there_are_sleep_durations() + { + var totalTimeSlept = 0; - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - policy.RaiseException(2); + policy.RaiseException(2); - totalTimeSlept.Should() - .Be(1 + 2); - } + totalTimeSlept.Should() + .Be(1 + 2); + } - [Fact] - public void Should_not_sleep_if_no_retries() - { - var totalTimeSlept = 0; + [Fact] + public void Should_not_sleep_if_no_retries() + { + var totalTimeSlept = 0; - var policy = Policy - .Handle() - .WaitAndRetry(Enumerable.Empty()); + var policy = Policy + .Handle() + .WaitAndRetry(Enumerable.Empty()); - SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; + SystemClock.Sleep = (span, _) => totalTimeSlept += span.Seconds; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - totalTimeSlept.Should() - .Be(0); - } + totalTimeSlept.Should() + .Be(0); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_timespan() - { - var expectedRetryWaits = new [] + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_timespan() { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }; + var expectedRetryWaits = new [] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, timeSpan) => actualRetryWaits.Add(timeSpan)); - policy.RaiseException(3); + policy.RaiseException(3); - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_exception() - { - var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; - var retryExceptions = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_exception() + { + var expectedExceptions = new object[] { "Exception #1", "Exception #2", "Exception #3" }; + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (exception, _) => retryExceptions.Add(exception)); - policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); + policy.RaiseException(3, (e, i) => e.HelpLink = "Exception #" + i); - retryExceptions - .Select(x => x.HelpLink) - .Should() - .ContainInOrder(expectedExceptions); - } + retryExceptions + .Select(x => x.HelpLink) + .Should() + .ContainInOrder(expectedExceptions); + } - [Fact] - public void Should_call_onretry_on_each_retry_with_the_current_retry_count() - { - var expectedRetryCounts = new[] { 1, 2, 3 }; - var retryCounts = new List(); + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, retryCount, _) => retryCounts.Add(retryCount)); - policy.RaiseException(3); + policy.RaiseException(3); - retryCounts.Should() - .ContainInOrder(expectedRetryCounts); - } + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } - [Fact] - public void Should_not_call_onretry_when_no_retries_are_performed() - { - var retryExceptions = new List(); + [Fact] + public void Should_not_call_onretry_when_no_retries_are_performed() + { + var retryExceptions = new List(); - var policy = Policy - .Handle() - .WaitAndRetry(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); + var policy = Policy + .Handle() + .WaitAndRetry(Enumerable.Empty(), (exception, _) => retryExceptions.Add(exception)); - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - retryExceptions.Should() - .BeEmpty(); - } + retryExceptions.Should() + .BeEmpty(); + } - [Fact] - public void Should_create_new_state_for_each_call_to_policy() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds() - }); + [Fact] + public void Should_create_new_state_for_each_call_to_policy() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds() + }); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); - policy.Invoking(x => x.RaiseException()) - .Should().NotThrow(); - } + policy.Invoking(x => x.RaiseException()) + .Should().NotThrow(); + } - [Fact] - public void Should_call_onretry_with_the_passed_context() - { - IDictionary contextData = null; + [Fact] + public void Should_call_onretry_with_the_passed_context() + { + IDictionary contextData = null; - var policy = Policy - .Handle() - .WaitAndRetry(new[] - { - 1.Seconds(), - 2.Seconds(), - 3.Seconds() - }, (_, _, context) => contextData = context); - - policy.RaiseException( - new { key1 = "value1", key2 = "value2" }.AsDictionary() - ); - - contextData.Should() - .ContainKeys("key1", "key2").And - .ContainValues("value1", "value2"); - } + var policy = Policy + .Handle() + .WaitAndRetry(new[] + { + 1.Seconds(), + 2.Seconds(), + 3.Seconds() + }, (_, _, context) => contextData = context); + + policy.RaiseException( + new { key1 = "value1", key2 = "value2" }.AsDictionary() + ); + + contextData.Should() + .ContainKeys("key1", "key2").And + .ContainValues("value1", "value2"); + } - [Fact] - public void Should_create_new_context_for_each_call_to_execute() - { - string contextValue = null; + [Fact] + public void Should_create_new_context_for_each_call_to_execute() + { + string contextValue = null; - var policy = Policy - .Handle() - .WaitAndRetry(new[] + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds() }, (_, _, context) => contextValue = context["key"].ToString()); - policy.RaiseException( - new { key = "original_value" }.AsDictionary() - ); - - contextValue.Should().Be("original_value"); - - policy.RaiseException( - new { key = "new_value" }.AsDictionary() - ); - - contextValue.Should().Be("new_value"); - } + policy.RaiseException( + new { key = "original_value" }.AsDictionary() + ); - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_without_context() - { - Action onRetry = (_, _) => { }; + contextValue.Should().Be("original_value"); - Action policy = () => Policy - .Handle() - .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); + policy.RaiseException( + new { key = "new_value" }.AsDictionary() + ); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + contextValue.Should().Be("new_value"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_retry_count_is_less_than_zero_with_attempts_with_context() - { - Action onRetry = (_, _, _, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("retryCount"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_without_context() - { - Action onRetry = (_, _) => { }; + [Fact] + public void Should_throw_when_retry_count_is_less_than_zero_with_attempts_with_context() + { + Action onRetry = (_, _, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(-1, _ => new TimeSpan(), onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("retryCount"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_context() - { - Action onRetry = (_, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_without_context() + { + Action onRetry = (_, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, (Func) null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_sleep_duration_provider_is_null_with_attempts_with_context() - { - Action onRetry = (_, _, _, _) => { }; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_context() + { + Action onRetry = (_, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, (Func)null, onRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, (Func) null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("sleepDurationProvider"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_sleep_duration_provider_is_null_with_attempts_with_context() + { + Action onRetry = (_, _, _, _) => { }; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, (Func)null, onRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("sleepDurationProvider"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_context_when_using_provider_overload() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_without_context_when_using_provider_overload() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_throw_when_onretry_action_is_null_with_attempts_with_context_when_using_provider_overload() - { - Action nullOnRetry = null; + [Fact] + public void Should_throw_when_onretry_action_is_null_with_context_when_using_provider_overload() + { + Action nullOnRetry = null; - Action policy = () => Policy - .Handle() - .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); - policy.Should().Throw().And - .ParamName.Should().Be("onRetry"); - } + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - [Fact] - public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() - { - var expectedRetryWaits = new[] + [Fact] + public void Should_throw_when_onretry_action_is_null_with_attempts_with_context_when_using_provider_overload() { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - - var actualRetryWaits = new List(); - - var policy = Policy - .Handle() - .WaitAndRetry(5, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (_, timeSpan) => actualRetryWaits.Add(timeSpan) - ); + Action nullOnRetry = null; - policy.RaiseException(5); - - actualRetryWaits.Should() - .ContainInOrder(expectedRetryWaits); - } + Action policy = () => Policy + .Handle() + .WaitAndRetry(1, _ => new TimeSpan(), nullOnRetry); - [Fact] - public void Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() - { - object capturedExceptionInstance = null; - - var exceptionInstance = new DivideByZeroException(); + policy.Should().Throw().And + .ParamName.Should().Be("onRetry"); + } - var policy = Policy - .Handle() - .WaitAndRetry(5, - sleepDurationProvider: (_, ex, _) => - { - capturedExceptionInstance = ex; - return TimeSpan.FromMilliseconds(0); - }, - onRetry: (_, _, _, _) => + [Fact] + public void Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider() + { + var expectedRetryWaits = new[] { - } - ); + 2.Seconds(), + 4.Seconds(), + 8.Seconds(), + 16.Seconds(), + 32.Seconds() + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetry(5, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (_, timeSpan) => actualRetryWaits.Add(timeSpan) + ); + + policy.RaiseException(5); + + actualRetryWaits.Should() + .ContainInOrder(expectedRetryWaits); + } - policy.RaiseException(exceptionInstance); + [Fact] + public void Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() + { + object capturedExceptionInstance = null; + + var exceptionInstance = new DivideByZeroException(); + + var policy = Policy + .Handle() + .WaitAndRetry(5, + sleepDurationProvider: (_, ex, _) => + { + capturedExceptionInstance = ex; + return TimeSpan.FromMilliseconds(0); + }, + onRetry: (_, _, _, _) => + { + } + ); + + policy.RaiseException(exceptionInstance); + + capturedExceptionInstance.Should().Be(exceptionInstance); + } - capturedExceptionInstance.Should().Be(exceptionInstance); - } + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; - {new DivideByZeroException(), 2.Seconds()}, - {new ArgumentNullException(), 4.Seconds()}, - }; + var actualRetryWaits = new List(); - var actualRetryWaits = new List(); + var policy = Policy + .Handle() + .WaitAndRetry(2, + (_, exc, _) => expectedRetryWaits[exc], + (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) + ); - var policy = Policy - .Handle() - .WaitAndRetry(2, - (_, exc, _) => expectedRetryWaits[exc], - (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) - ); + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } - - [Fact] - public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() - { - var expectedRetryDuration = 1.Seconds(); - TimeSpan? actualRetryDuration = null; + [Fact] + public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() + { + var expectedRetryDuration = 1.Seconds(); + TimeSpan? actualRetryDuration = null; - var defaultRetryAfter = 30.Seconds(); + var defaultRetryAfter = 30.Seconds(); - var policy = Policy - .Handle() - .WaitAndRetry(1, - sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. - onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. - ); + var policy = Policy + .Handle() + .WaitAndRetry(1, + sleepDurationProvider: (_, context) => context.ContainsKey("RetryAfter") ? (TimeSpan)context["RetryAfter"] : defaultRetryAfter, // Set sleep duration from Context, when available. + onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. + ); - var failedOnce = false; - policy.Execute(context => + var failedOnce = false; + policy.Execute(context => { // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. context["RetryAfter"] = expectedRetryDuration; @@ -785,416 +785,417 @@ public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDuratio throw new DivideByZeroException(); } }, - new { RetryAfter = defaultRetryAfter }.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. - ); - - actualRetryDuration.Should().Be(expectedRetryDuration); - } - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_without_context() - { - var retryInvoked = false; - - Action onRetry = (_, _) => { retryInvoked = true; }; + new { RetryAfter = defaultRetryAfter }.AsDictionary() // Can also set an initial value for RetryAfter, in the Context passed into the call. + ); - var policy = Policy - .Handle() - .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); + actualRetryDuration.Should().Be(expectedRetryDuration); + } + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_without_context() + { + var retryInvoked = false; - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + Action onRetry = (_, _) => { retryInvoked = true; }; - retryInvoked.Should().BeFalse(); - } - - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_context() - { - var retryInvoked = false; + var policy = Policy + .Handle() + .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); - Action onRetry = (_, _, _) => { retryInvoked = true; }; + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - Policy policy = Policy - .Handle() - .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); + retryInvoked.Should().BeFalse(); + } - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_context() + { + var retryInvoked = false; - retryInvoked.Should().BeFalse(); - } + Action onRetry = (_, _, _) => { retryInvoked = true; }; - [Fact] - public void Should_not_call_onretry_when_retry_count_is_zero_with_attempts_with_context() - { - var retryInvoked = false; + Policy policy = Policy + .Handle() + .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); - Action onRetry = (_, _, _, _) => { retryInvoked = true; }; + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - Policy policy = Policy - .Handle() - .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); + retryInvoked.Should().BeFalse(); + } - policy.Invoking(x => x.RaiseException()) - .Should().Throw(); + [Fact] + public void Should_not_call_onretry_when_retry_count_is_zero_with_attempts_with_context() + { + var retryInvoked = false; - retryInvoked.Should().BeFalse(); - } + Action onRetry = (_, _, _, _) => { retryInvoked = true; }; - #region Sync cancellation tests + Policy policy = Policy + .Handle() + .WaitAndRetry(0, _ => TimeSpan.FromSeconds(1), onRetry); - [Fact] - public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + policy.Invoking(x => x.RaiseException()) + .Should().Throw(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + retryInvoked.Should().BeFalse(); + } - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + #region Sync cancellation tests - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - cancellationTokenSource.Cancel(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - attemptsInvoked.Should().Be(0); - } + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. + }; - [Fact] - public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + cancellationTokenSource.Cancel(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(0); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 2, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(2); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 2, + ActionObservesCancellation = false + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(2); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = true - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = true + }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + attemptsInvoked.Should().Be(1 + 3); + } - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + [Fact] + public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = 1 + 3, - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw(); + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - attemptsInvoked.Should().Be(1 + 3); - } + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - [Fact] - public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = 1 + 3, + ActionObservesCancellation = false + }; - SystemClock.Sleep = (timeSpan, ct) => Task.Delay(timeSpan, ct).Wait(ct); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw(); - var shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; + attemptsInvoked.Should().Be(1 + 3); + } - var policy = Policy - .Handle() - .WaitAndRetry(new[] { retryDelay }); + [Fact] + public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() + { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + SystemClock.Sleep = (timeSpan, ct) => Task.Delay(timeSpan, ct).Wait(ct); - var watch = new Stopwatch(); - watch.Start(); + var shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 1, - AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. - ActionObservesCancellation = false - }; + var policy = Policy + .Handle() + .WaitAndRetry(new[] { retryDelay }); - cancellationTokenSource.CancelAfter(shimTimeSpan); + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); - watch.Stop(); + var watch = new Stopwatch(); + watch.Start(); - attemptsInvoked.Should().Be(1); + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 1, + AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. + ActionObservesCancellation = false + }; - watch.Elapsed.Should().BeLessThan(retryDelay); - watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: (int)(shimTimeSpan.TotalMilliseconds) / 2); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. - } + cancellationTokenSource.CancelAfter(shimTimeSpan); - [Fact] - public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() - { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); + watch.Stop(); + + attemptsInvoked.Should().Be(1); - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, + watch.Elapsed.Should().BeLessThan(retryDelay); + watch.Elapsed.Should().BeCloseTo(shimTimeSpan, precision: (int)(shimTimeSpan.TotalMilliseconds) / 2); // Consider increasing shimTimeSpan, or loosening precision, if test fails transiently in different environments. + } + + [Fact] + public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() + { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; + + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }, (_, _) => { cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 1 + 3, - AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. - ActionObservesCancellation = false - }; + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 1 + 3, + AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. + ActionObservesCancellation = false + }; - policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) - .Should().Throw() - .And.CancellationToken.Should().Be(cancellationToken); + policy.Invoking(x => x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute)) + .Should().Throw() + .And.CancellationToken.Should().Be(cancellationToken); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + [Fact] + public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = null - }; + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = null + }; - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().NotThrow(); + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().NotThrow(); - result.Should().BeTrue(); + result.Should().BeTrue(); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - [Fact] - public void Should_honour_and_report_cancellation_during_func_execution() - { - var policy = Policy - .Handle() - .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); + [Fact] + public void Should_honour_and_report_cancellation_during_func_execution() + { + var policy = Policy + .Handle() + .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; - Action onExecute = () => attemptsInvoked++; + var attemptsInvoked = 0; + Action onExecute = () => attemptsInvoked++; - bool? result = null; + bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario - { - NumberOfTimesToRaiseException = 0, - AttemptDuringWhichToCancel = 1, - ActionObservesCancellation = true - }; + var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + { + NumberOfTimesToRaiseException = 0, + AttemptDuringWhichToCancel = 1, + ActionObservesCancellation = true + }; - policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) - .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); + policy.Invoking(x => result = x.RaiseExceptionAndOrCancellation(scenario, cancellationTokenSource, onExecute, true)) + .Should().Throw().And.CancellationToken.Should().Be(cancellationToken); - result.Should().Be(null); + result.Should().Be(null); - attemptsInvoked.Should().Be(1); - } + attemptsInvoked.Should().Be(1); + } - #endregion + #endregion - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); + } + } } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs index e187312e7fc..dc9169512a1 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs @@ -7,55 +7,56 @@ using Xunit; using FluentAssertions.Extensions; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetryTResultAsyncSpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetryTResultAsyncSpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetryTResultAsyncSpecs : IDisposable { - // do nothing on call to sleep - SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; - } - - [Fact] - public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ - - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; - - var actualRetryWaits = new List(); + public WaitAndRetryTResultAsyncSpecs() + { + // do nothing on call to sleep + SystemClock.SleepAsync = (_, _) => TaskHelper.EmptyTask; + } - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetryAsync(2, - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _, _) => + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryAsync(2, + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _, _) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(async () => { - actualRetryWaits.Add(timeSpan); - return TaskHelper.EmptyTask; + await TaskHelper.EmptyTask; + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; }); + } - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - await policy.ExecuteAsync(async () => - { - await TaskHelper.EmptyTask; - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; - }); + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } + public void Dispose() + { + SystemClock.Reset(); + } - public void Dispose() - { - SystemClock.Reset(); } - } \ No newline at end of file diff --git a/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs index a1a1bfdf466..f1c7f13bcb7 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs @@ -6,51 +6,52 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Retry; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class WaitAndRetryTResultSpecs : IDisposable +namespace Polly.Specs.Retry { - public WaitAndRetryTResultSpecs() + [Collection(Constants.SystemClockDependentTestCollection)] + public class WaitAndRetryTResultSpecs : IDisposable { - // do nothing on call to sleep - SystemClock.Sleep = (_, _) => { }; - } + public WaitAndRetryTResultSpecs() + { + // do nothing on call to sleep + SystemClock.Sleep = (_, _) => { }; + } - [Fact] - public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() - { - var expectedRetryWaits = new Dictionary(){ + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + var expectedRetryWaits = new Dictionary(){ - {ResultPrimitive.Fault, 2.Seconds()}, - {ResultPrimitive.FaultAgain, 4.Seconds()}, - }; + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; - var actualRetryWaits = new List(); + var actualRetryWaits = new List(); - var policy = Policy - .HandleResult(ResultPrimitive.Fault) - .OrResult(ResultPrimitive.FaultAgain) - .WaitAndRetry(2, - (_, outcome, _) => expectedRetryWaits[outcome.Result], - (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) - ); + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetry(2, + (_, outcome, _) => expectedRetryWaits[outcome.Result], + (_, timeSpan, _, _) => actualRetryWaits.Add(timeSpan) + ); - using (var enumerator = expectedRetryWaits.GetEnumerator()) - { - policy.Execute(() => + using (var enumerator = expectedRetryWaits.GetEnumerator()) { - if (enumerator.MoveNext()) return enumerator.Current.Key; - else return ResultPrimitive.Undefined; - }); + policy.Execute(() => + { + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } - actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); - } + public void Dispose() + { + SystemClock.Reset(); + } - public void Dispose() - { - SystemClock.Reset(); } - } \ No newline at end of file diff --git a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs index e36c553d871..fc9edef40c8 100644 --- a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs @@ -8,811 +8,812 @@ using System.Threading.Tasks; using Xunit; -namespace Polly.Specs.Timeout; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class TimeoutAsyncSpecs : TimeoutSpecsBase +namespace Polly.Specs.Timeout { - #region Configuration - - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() + [Collection(Constants.SystemClockDependentTestCollection)] + public class TimeoutAsyncSpecs : TimeoutSpecsBase { - Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); - - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + #region Configuration - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(0); + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(0); - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(-10); + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(-10); - policy.Should().NotThrow(); - } + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(3); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(3); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(int.MaxValue); + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(int.MaxValue); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.TimeoutAsync((Func)null); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.TimeoutAsync((Func)null); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); - policy.Should().NotThrow(); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - #endregion + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); - #region Timeout operation - pessimistic + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() - { - var timeout = TimeSpan.FromMilliseconds(50); + #endregion - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + #region Timeout operation - pessimistic - policy.Awaiting(p => p.ExecuteAsync(async () => + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + var timeout = TimeSpan.FromMilliseconds(50); - })).Should().Throw(); - } + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - var result = ResultPrimitive.Undefined; + })).Should().Throw(); + } - var act = async () => + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() { - result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - }; + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + var result = ResultPrimitive.Undefined; - [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() - { - var watch = new Stopwatch(); + var act = async () => + { + result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + }; + + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_timeout_after_correct_duration__pessimistic() + { + var watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async () => + watch.Start(); + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); })) - .Should().Throw(); - watch.Stop(); + .Should().Throw(); + watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + [Fact] + public void Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); - } + policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + } - #endregion + #endregion - #region Timeout operation - optimistic + #region Timeout operation - optimistic - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() - { - var timeout = TimeSpan.FromMilliseconds(50); + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + { + var timeout = TimeSpan.FromMilliseconds(50); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); }, userCancellationToken)) .Should().Throw(); - } - - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; - - var act = async () => { - result = await policy.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + } - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; + + var act = async () => { + result = await policy.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; + + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() - { - var watch = new Stopwatch(); + [Fact] + public void Should_throw_timeout_after_correct_duration__optimistic() + { + var watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async ct => + watch.Start(); + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); }, userCancellationToken)) - .Should().Throw(); - watch.Stop(); + .Should().Throw(); + watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } - - [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - #endregion + policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + } - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + #endregion - [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() - { - var timeout = 5; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { - policy.Awaiting(p => p.ExecuteAsync(async + var timeout = 5; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Awaiting(p => p.ExecuteAsync(async _ => { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - }, userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + }, userTokenSource.Token) + ).Should().Throw(); // ... it's still the timeout we expect. + } } - } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(_ => + policy.Awaiting(p => p.ExecuteAsync(_ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) .Should().Throw(); - } + } - executed.Should().BeFalse(); - } - - #endregion + executed.Should().BeFalse(); + } - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) + #endregion - [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() - { - var timeout = 10; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - policy.Awaiting(p => p.ExecuteAsync( + var timeout = 10; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Awaiting(p => p.ExecuteAsync( ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution return TaskHelper.EmptyTask; }, userTokenSource.Token) // ... with user token. - ).Should().Throw(); + ).Should().Throw(); + } } - } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(_ => + policy.Awaiting(p => p.ExecuteAsync(_ => { executed = true; return TaskHelper.EmptyTask; }, cts.Token)) .Should().Throw(); - } + } - executed.Should().BeFalse(); - } + executed.Should().BeFalse(); + } - [Fact] - public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() - { - var userException = new Exception(); - var shimTimeSpan = TimeSpan.FromSeconds(0.2); - var policy = Policy.TimeoutAsync(shimTimeSpan, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() + { + var userException = new Exception(); + var shimTimeSpan = TimeSpan.FromSeconds(0.2); + var policy = Policy.TimeoutAsync(shimTimeSpan, TimeoutStrategy.Optimistic); - var thrown = policy.Awaiting(p => p.ExecuteAsync(async _ => - { - try - { - await SystemClock.SleepAsync(shimTimeSpan + shimTimeSpan, CancellationToken.None); - } - catch + var thrown = policy.Awaiting(p => p.ExecuteAsync(async _ => { - // Throw a different exception - this exception should not be masked. - // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown - // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. - // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. - throw userException; - } - - throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); - - }, CancellationToken.None)) - .Should() - .Throw() - .Which; - - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().BeSameAs(userException); - } - - #endregion + try + { + await SystemClock.SleepAsync(shimTimeSpan + shimTimeSpan, CancellationToken.None); + } + catch + { + // Throw a different exception - this exception should not be masked. + // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown + // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. + // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. + throw userException; + } + + throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); + + }, CancellationToken.None)) + .Should() + .Throw() + .Which; + + thrown.Should().NotBeOfType(); + thrown.Should().NotBeOfType(); + thrown.Should().NotBeOfType(); + thrown.Should().BeSameAs(userException); + } - #region onTimeout overload - pessimistic + #endregion - [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + #region onTimeout overload - pessimistic - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Fact] + public void Should_call_ontimeout_with_configured_timeout__pessimistic() { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); })) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => + [Fact] + public void Should_call_ontimeout_with_passed_context__pessimistic() { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(p => p.ExecuteAsync(async _ => + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - }, contextPassedToExecute)) - .Should().Throw(); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + policy.Awaiting(p => p.ExecuteAsync(async _ => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + }, contextPassedToExecute)) + .Should().Throw(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); })) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - policy.Awaiting(p => p.ExecuteAsync(async _ => + policy.Awaiting(p => p.ExecuteAsync(async _ => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); }, context)) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); })) .Should().Throw(); - taskPassedToOnTimeout.Should().NotBeNull(); - } + taskPassedToOnTimeout.Should().NotBeNull(); + } - [Fact] - public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. - // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + [Fact] + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. + // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - Exception exceptionToThrow = new DivideByZeroException(); + Exception exceptionToThrow = new DivideByZeroException(); - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. - return TaskHelper.EmptyTask; - }; + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. + return TaskHelper.EmptyTask; + }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); throw exceptionToThrow; })) .Should().Throw(); - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - - } + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + } - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(p => p.ExecuteAsync(async () => + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - })) - .Should().Throw(); + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + + })) + .Should().Throw(); - #endregion + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #region onTimeout overload - optimistic + #endregion - [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + #region onTimeout overload - optimistic - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Fact] + public void Should_call_ontimeout_with_configured_timeout__optimistic() { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); }, userCancellationToken)) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => + [Fact] + public void Should_call_ontimeout_with_passed_context__optimistic() { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - }, contextPassedToExecute, userCancellationToken)) - .Should().Throw(); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + }, contextPassedToExecute, userCancellationToken)) + .Should().Throw(); - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + var timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); - policy.Awaiting(p => p.ExecuteAsync(async ct => + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - }, userCancellationToken)) - .Should().Throw(); + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + }, userCancellationToken)) + .Should().Throw(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); }, context, userCancellationToken)) - .Should().Throw(); + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => + [Fact] + public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); }, userCancellationToken)) .Should().Throw(); - taskPassedToOnTimeout.Should().BeNull(); - } - - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + taskPassedToOnTimeout.Should().BeNull(); + } - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); }, userCancellationToken)) .Should().Throw(); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Timeout/TimeoutSpecs.cs b/src/Polly.Specs/Timeout/TimeoutSpecs.cs index 2accf7efb64..1ceffb35417 100644 --- a/src/Polly.Specs/Timeout/TimeoutSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutSpecs.cs @@ -8,791 +8,792 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Timeout; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class TimeoutSpecs : TimeoutSpecsBase +namespace Polly.Specs.Timeout { - #region Configuration - - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() + [Collection(Constants.SystemClockDependentTestCollection)] + public class TimeoutSpecs : TimeoutSpecsBase { - Action policy = () => Policy.Timeout(TimeSpan.Zero); + #region Configuration - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() + { + Action policy = () => Policy.Timeout(TimeSpan.Zero); - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.Timeout(0); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.Timeout(0); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(-10); + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(-10); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(3); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(3); - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.Timeout(TimeSpan.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.Timeout(TimeSpan.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.Timeout(int.MaxValue); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.Timeout(int.MaxValue); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout_overload() - { - Action doNothing = (_, _, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout_overload() + { + Action doNothing = (_, _, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout_overload() - { - Action doNothing = (_, _, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); + policy.Should().NotThrow(); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout_overload() + { + Action doNothing = (_, _, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().NotThrow(); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_overload_is_null_with_timespan() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_timespan() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_overload_is_null_with_seconds() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_seconds() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.Timeout((Func)null); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.Timeout((Func)null); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_throw_when_onTimeout_overload_is_null_with_timeoutprovider() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_timeoutprovider() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - policy.Should().NotThrow(); - } + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); - #endregion + policy.Should().NotThrow(); + } - #region Timeout operation - pessimistic + #endregion - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Pessimistic); + #region Timeout operation - pessimistic - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); + } - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - var act = () => { - result = policy.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + var act = () => { + result = policy.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; - [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() - { - var watch = new Stopwatch(); + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + [Fact] + public void Should_throw_timeout_after_correct_duration__pessimistic() + { + var watch = new Stopwatch(); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - watch.Start(); - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None))) - .Should().Throw(); - watch.Stop(); + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } + watch.Start(); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None))) + .Should().Throw(); + watch.Stop(); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + } - // Check to see if nested aggregate exceptions are unwrapped correctly - var exception = new AggregateException(msg, new NotImplementedException()); + [Fact] + public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; - policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); })) - .Should().Throw() - .WithMessage(exception.Message) - .Where(e => e.InnerException is NotImplementedException) - .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); - } + // Check to see if nested aggregate exceptions are unwrapped correctly + var exception = new AggregateException(msg, new NotImplementedException()); - [Fact] - public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; - - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - var aggregateException = new AggregateException(msg, innerException1, innerException2); - Action action = () => throw aggregateException; - - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - action.Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - - policy.Invoking(p => p.Execute(action)).Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); })) + .Should().Throw() + .WithMessage(exception.Message) + .Where(e => e.InnerException is NotImplementedException) + .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); + } - [Fact] - public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + [Fact] + public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; + + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + var aggregateException = new AggregateException(msg, innerException1, innerException2); + Action action = () => throw aggregateException; + + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + action.Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + + policy.Invoking(p => p.Execute(action)).Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - var action = () => + [Fact] + public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() { - var task1 = Task.Run(() => throw innerException1); - var task2 = Task.Run(() => throw innerException2); - Task.WhenAll(task1, task2).Wait(); - }; + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - action.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + var action = () => + { + var task1 = Task.Run(() => throw innerException1); + var task2 = Task.Run(() => throw innerException2); + Task.WhenAll(task1, task2).Wait(); + }; - policy.Invoking(p => p.Execute(action)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + action.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - [Fact] - public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + policy.Invoking(p => p.Execute(action)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - var action = () => + [Fact] + public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() { - Action action1 = () => throw innerException1; - Action action2 = () => throw innerException2; - Parallel.Invoke(action1, action2); - }; + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - action.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + var action = () => + { + Action action1 = () => throw innerException1; + Action action2 = () => throw innerException2; + Parallel.Invoke(action1, action2); + }; - policy.Invoking(p => p.Execute(action)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + action.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - #endregion + policy.Invoking(p => p.Execute(action)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - #region Timeout operation - optimistic + #endregion - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + #region Timeout operation - optimistic - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. - .Should().Throw(); - } + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. + .Should().Throw(); + } - var act = () => { - result = policy.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; + + var act = () => { + result = policy.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; + + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + [Fact] + public void Should_throw_timeout_after_correct_duration__optimistic() + { + var watch = new Stopwatch(); - [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() - { - var watch = new Stopwatch(); + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout); + var userCancellationToken = CancellationToken.None; - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout); - var userCancellationToken = CancellationToken.None; + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + watch.Start(); + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(10), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. + .Should().Throw(); + watch.Stop(); - watch.Start(); - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(10), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. - .Should().Throw(); - watch.Stop(); + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + } - } + #endregion - #endregion + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + [Fact] + public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() + { + var timeout = 5; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() - { - var timeout = 5; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Invoking(p => p.Execute( + _ => + { + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + } + , userTokenSource.Token) + ).Should().Throw(); // ... it's still the timeout we expect. + } + } - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() { - policy.Invoking(p => p.Execute( - _ => - { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - } - , userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. - } - } + var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() - { - var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); + var executed = false; - var executed = false; + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) + .Should().Throw(); + } - policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) - .Should().Throw(); + executed.Should().BeFalse(); } - executed.Should().BeFalse(); - } - - #endregion + #endregion - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - - [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() - { - var timeout = 10; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - policy.Invoking(p => p.Execute( + var timeout = 10; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); + + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Invoking(p => p.Execute( ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); } // Simulate cancel in the middle of execution , userTokenSource.Token) // ... with user token. - ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. + ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. + } } - } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() - { - var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) - .Should().Throw(); - } + policy.Invoking(p => p.Execute(_ => { executed = true; }, cts.Token)) + .Should().Throw(); + } - executed.Should().BeFalse(); - } + executed.Should().BeFalse(); + } - [Fact] - public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() - { - var userException = new Exception(); - var shimTimeSpan = TimeSpan.FromSeconds(0.2); - var policy = Policy.Timeout(shimTimeSpan, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeout() + { + var userException = new Exception(); + var shimTimeSpan = TimeSpan.FromSeconds(0.2); + var policy = Policy.Timeout(shimTimeSpan, TimeoutStrategy.Optimistic); - var thrown = policy.Invoking(p => p.Execute(_ => - { - try - { - SystemClock.Sleep(shimTimeSpan + shimTimeSpan, CancellationToken.None); - } - catch + var thrown = policy.Invoking(p => p.Execute(_ => { - // Throw a different exception - this exception should not be masked. - // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown - // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. - // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. - throw userException; - } - - throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); - - }, CancellationToken.None)) - .Should() - .Throw() - .Which; - - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().NotBeOfType(); - thrown.Should().BeSameAs(userException); - } + try + { + SystemClock.Sleep(shimTimeSpan + shimTimeSpan, CancellationToken.None); + } + catch + { + // Throw a different exception - this exception should not be masked. + // The issue of more interest for issue 620 is an edge-case race condition where a user exception is thrown + // quasi-simultaneously to timeout (rather than in a manual catch of a timeout as here), but this is a good way to simulate it. + // A real-world case can be if timeout occurs while code is marshalling a user-exception into or through the catch block in TimeoutEngine. + throw userException; + } + + throw new InvalidOperationException("This exception should not be thrown. Test should throw for timeout earlier."); + + }, CancellationToken.None)) + .Should() + .Throw() + .Which; + + thrown.Should().NotBeOfType(); + thrown.Should().NotBeOfType(); + thrown.Should().NotBeOfType(); + thrown.Should().BeSameAs(userException); + } - #endregion + #endregion - #region onTimeout overload - pessimistic + #region onTimeout overload - pessimistic - [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_configured_timeout__pessimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + [Fact] + public void Should_call_ontimeout_with_passed_context__pessimistic() + { + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), contextPassedToExecute)) - .Should().Throw(); + policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), contextPassedToExecute)) + .Should().Throw(); - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + var timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") {["timeout"] = TimeSpan.FromMilliseconds(25* programaticallyControlledDelay) }; + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") {["timeout"] = TimeSpan.FromMilliseconds(25* programaticallyControlledDelay) }; - policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), context)) - .Should().Throw(); + policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), context)) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - taskPassedToOnTimeout.Should().NotBeNull(); - } + taskPassedToOnTimeout.Should().NotBeNull(); + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. - // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. + // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - Exception exceptionToThrow = new DivideByZeroException(); + Exception exceptionToThrow = new DivideByZeroException(); - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); - }; + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); + }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); + var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => + policy.Invoking(p => p.Execute(() => { SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); throw exceptionToThrow; })) - .Should().Throw(); + .Should().Throw(); - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - } + } - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .Should().Throw(); + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .Should().Throw(); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion - #region onTimeout overload - optimistic + #region onTimeout overload - optimistic - [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_configured_timeout__optimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) - .Should().Throw(); + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + [Fact] + public void Should_call_ontimeout_with_passed_context__optimistic() + { + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), contextPassedToExecute, userCancellationToken)) - .Should().Throw(); + policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), contextPassedToExecute, userCancellationToken)) + .Should().Throw(); - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + var timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) - .Should().Throw(); + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var userCancellationToken = CancellationToken.None; - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) - .Should().Throw(); + policy.Invoking(p => p.Execute((_, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + [Fact] + public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) - .Should().Throw(); + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) + .Should().Throw(); - taskPassedToOnTimeout.Should().BeNull(); - } + taskPassedToOnTimeout.Should().BeNull(); + } - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) - .Should().Throw(); + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) + .Should().Throw(); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion -} \ No newline at end of file + #endregion + } +} diff --git a/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs b/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs index c4fe4798384..adadf33bf60 100644 --- a/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs +++ b/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs @@ -4,99 +4,100 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Specs.Timeout; - -/// -/// Provides common functionality for timeout specs, which abstracts out both SystemClock.Sleep, and SystemClock.CancelTokenAfter. -/// Polly's TimeoutPolicy uses timing-out CancellationTokens to drive timeouts. -/// For tests, rather than letting .NET's timers drive the timing out of CancellationTokens, we override SystemClock.CancelTokenAfter and SystemClock.Sleep to make the tests run fast. -/// -/// -public abstract class TimeoutSpecsBase : IDisposable +namespace Polly.Specs.Timeout { - // xUnit creates a new class instance per test, so these variables are isolated per test. + /// + /// Provides common functionality for timeout specs, which abstracts out both SystemClock.Sleep, and SystemClock.CancelTokenAfter. + /// Polly's TimeoutPolicy uses timing-out CancellationTokens to drive timeouts. + /// For tests, rather than letting .NET's timers drive the timing out of CancellationTokens, we override SystemClock.CancelTokenAfter and SystemClock.Sleep to make the tests run fast. + /// + /// + public abstract class TimeoutSpecsBase : IDisposable + { + // xUnit creates a new class instance per test, so these variables are isolated per test. - // Track a CancellationTokenSource, and when it might be cancelled at. - private CancellationTokenSource _trackedTokenSource; - private DateTimeOffset _cancelAt = DateTimeOffset.MaxValue; + // Track a CancellationTokenSource, and when it might be cancelled at. + private CancellationTokenSource _trackedTokenSource; + private DateTimeOffset _cancelAt = DateTimeOffset.MaxValue; - private DateTimeOffset _offsetUtcNow = DateTimeOffset.UtcNow; - private DateTime _utcNow = DateTime.UtcNow; + private DateTimeOffset _offsetUtcNow = DateTimeOffset.UtcNow; + private DateTime _utcNow = DateTime.UtcNow; - protected TimeoutSpecsBase() - { - // Override the SystemClock, to return time stored in variables we manipulate. - SystemClock.DateTimeOffsetUtcNow = () => _offsetUtcNow; - SystemClock.UtcNow = () => _utcNow; - - // Override SystemClock.CancelTokenAfter to record when the policy wants the token to cancel. - SystemClock.CancelTokenAfter = (tokenSource, timespan) => + protected TimeoutSpecsBase() { - if (_trackedTokenSource != null && tokenSource != _trackedTokenSource) throw new InvalidOperationException("Timeout tests cannot track more than one timing out token at a time."); + // Override the SystemClock, to return time stored in variables we manipulate. + SystemClock.DateTimeOffsetUtcNow = () => _offsetUtcNow; + SystemClock.UtcNow = () => _utcNow; - _trackedTokenSource = tokenSource; + // Override SystemClock.CancelTokenAfter to record when the policy wants the token to cancel. + SystemClock.CancelTokenAfter = (tokenSource, timespan) => + { + if (_trackedTokenSource != null && tokenSource != _trackedTokenSource) throw new InvalidOperationException("Timeout tests cannot track more than one timing out token at a time."); - var newCancelAt = _offsetUtcNow.Add(timespan); - _cancelAt = newCancelAt < _cancelAt ? newCancelAt : _cancelAt; + _trackedTokenSource = tokenSource; - SystemClock.Sleep(TimeSpan.Zero, CancellationToken.None); // Invoke our custom definition of sleep, to check for immediate cancellation. - }; + var newCancelAt = _offsetUtcNow.Add(timespan); + _cancelAt = newCancelAt < _cancelAt ? newCancelAt : _cancelAt; - // Override SystemClock.Sleep, to manipulate our artificial clock. And - if it means sleeping beyond the time when a tracked token should cancel - cancel it! - SystemClock.Sleep = (sleepTimespan, sleepCancellationToken) => - { - if (sleepCancellationToken.IsCancellationRequested) return; + SystemClock.Sleep(TimeSpan.Zero, CancellationToken.None); // Invoke our custom definition of sleep, to check for immediate cancellation. + }; - if (_trackedTokenSource == null || _trackedTokenSource.IsCancellationRequested) - { - // Not tracking any CancellationToken (or already cancelled) - just advance time. - _utcNow += sleepTimespan; - _offsetUtcNow += sleepTimespan; - } - else + // Override SystemClock.Sleep, to manipulate our artificial clock. And - if it means sleeping beyond the time when a tracked token should cancel - cancel it! + SystemClock.Sleep = (sleepTimespan, sleepCancellationToken) => { - // Tracking something to cancel - does this sleep hit time to cancel? - var timeToCancellation = _cancelAt - _offsetUtcNow; - if (sleepTimespan >= timeToCancellation) - { - // Cancel! (And advance time only to the instant of cancellation) - _offsetUtcNow += timeToCancellation; - _utcNow += timeToCancellation; + if (sleepCancellationToken.IsCancellationRequested) return; - // (and stop tracking it after cancelling; it can't be cancelled twice, so there is no need, and the owner may dispose it) - var copySource = _trackedTokenSource; - _trackedTokenSource = null; - copySource.Cancel(); - copySource.Token.ThrowIfCancellationRequested(); - } - else + if (_trackedTokenSource == null || _trackedTokenSource.IsCancellationRequested) { - // (not yet time to cancel - just advance time) + // Not tracking any CancellationToken (or already cancelled) - just advance time. _utcNow += sleepTimespan; _offsetUtcNow += sleepTimespan; } - } - }; + else + { + // Tracking something to cancel - does this sleep hit time to cancel? + var timeToCancellation = _cancelAt - _offsetUtcNow; + if (sleepTimespan >= timeToCancellation) + { + // Cancel! (And advance time only to the instant of cancellation) + _offsetUtcNow += timeToCancellation; + _utcNow += timeToCancellation; + + // (and stop tracking it after cancelling; it can't be cancelled twice, so there is no need, and the owner may dispose it) + var copySource = _trackedTokenSource; + _trackedTokenSource = null; + copySource.Cancel(); + copySource.Token.ThrowIfCancellationRequested(); + } + else + { + // (not yet time to cancel - just advance time) + _utcNow += sleepTimespan; + _offsetUtcNow += sleepTimespan; + } + } + }; - SystemClock.SleepAsync = (sleepTimespan, cancellationToken) => - { - SystemClock.Sleep(sleepTimespan, cancellationToken); - return Task.FromResult(true); - }; - } + SystemClock.SleepAsync = (sleepTimespan, cancellationToken) => + { + SystemClock.Sleep(sleepTimespan, cancellationToken); + return Task.FromResult(true); + }; + } - public void Dispose() - { - SystemClock.Reset(); - } + public void Dispose() + { + SystemClock.Reset(); + } - /// - /// A helper method which simply throws the passed exception. Supports tests verifying the stack trace of where an exception was thrown, by throwing that exception from a specific (other) location. - /// - /// The exception to throw. - [MethodImpl(MethodImplOptions.NoInlining)] // Tests that use this method assert that the exception was thrown from within this method; therefore, it is essential that - protected void Helper_ThrowException(Exception ex) - { - throw ex; + /// + /// A helper method which simply throws the passed exception. Supports tests verifying the stack trace of where an exception was thrown, by throwing that exception from a specific (other) location. + /// + /// The exception to throw. + [MethodImpl(MethodImplOptions.NoInlining)] // Tests that use this method assert that the exception was thrown from within this method; therefore, it is essential that + protected void Helper_ThrowException(Exception ex) + { + throw ex; + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs index acda1c05976..09c3cd7376e 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -8,771 +8,772 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Timeout; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class TimeoutTResultAsyncSpecs : TimeoutSpecsBase +namespace Polly.Specs.Timeout { - #region Configuration - - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() + [Collection(Constants.SystemClockDependentTestCollection)] + public class TimeoutTResultAsyncSpecs : TimeoutSpecsBase { - Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); + #region Configuration - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } - - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(0); + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.Zero); - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(0); - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(-10); + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(-TimeSpan.FromHours(1)); - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(-10); - policy.Should().NotThrow(); - } + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.TimeoutAsync(3); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMilliseconds(1)); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.TimeoutAsync(3); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.TimeoutAsync(int.MaxValue); + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(TimeSpan.MaxValue); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.TimeoutAsync(int.MaxValue); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; - Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, doNothingAsync); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Func doNothingAsync = (_, _, _) => TaskHelper.EmptyTask; + Action policy = () => Policy.TimeoutAsync(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothingAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() - { - Func onTimeout = null; - Action policy = () => Policy.TimeoutAsync(30, onTimeout); + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.TimeoutAsync((Func)null); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Func onTimeoutAsync = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.TimeoutAsync((Func)null); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_for_full_argument_list_onTimeout() - { - Func onTimeoutAsync = null; - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Func onTimeoutAsync = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeoutAsync"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_for_full_argument_list_onTimeout() + { + Func onTimeoutAsync = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); - policy.Should().NotThrow(); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeoutAsync"); + } - #endregion + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(1)); - #region Timeout operation - pessimistic + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() - { - var timeout = TimeSpan.FromMilliseconds(50); + #endregion - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + #region Timeout operation - pessimistic - policy.Awaiting(p => p.ExecuteAsync(async () => + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })).Should().Throw(); - } + var timeout = TimeSpan.FromMilliseconds(50); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + + policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })).Should().Throw(); + } - var result = ResultPrimitive.Undefined; + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - Func act = async () => result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); + var result = ResultPrimitive.Undefined; - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + Func act = async () => result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); - [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() - { - var watch = new Stopwatch(); + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_timeout_after_correct_duration__pessimistic() + { + var watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async () => + watch.Start(); + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw(); - watch.Stop(); + .Should().Throw(); + watch.Stop(); - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } - - [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - #endregion + policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + } - #region Timeout operation - optimistic + #endregion - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + #region Timeout operation - optimistic - policy.Awaiting(p => p.ExecuteAsync(async ct => + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)).Should().Throw(); - } + var policy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)).Should().Throw(); + } - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - Func act = async () => result = await policy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), userCancellationToken); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + Func act = async () => result = await policy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), userCancellationToken); - [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() - { - var watch = new Stopwatch(); + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_timeout_after_correct_duration__optimistic() + { + var watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Start(); - policy.Awaiting(p => p.ExecuteAsync(async ct => + watch.Start(); + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(10), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) - .Should().Throw(); - watch.Stop(); - - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } + .Should().Throw(); + watch.Stop(); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() - { - var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - #endregion + policy.Awaiting(p => p.ExecuteAsync(() => throw new NotImplementedException())).Should().Throw(); + } - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + #endregion - [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() - { - var timeout = 5; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { - policy.Awaiting(p => p.ExecuteAsync(async + var timeout = 5; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); + + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Awaiting(p => p.ExecuteAsync(async _ => { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - return ResultPrimitive.WhateverButTooLate; - }, userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + await SystemClock.SleepAsync(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + return ResultPrimitive.WhateverButTooLate; + }, userTokenSource.Token) + ).Should().Throw(); // ... it's still the timeout we expect. + } } - } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(async _ => + policy.Awaiting(p => p.ExecuteAsync(async _ => { executed = true; await TaskHelper.EmptyTask; return ResultPrimitive.WhateverButTooLate; }, cts.Token)) .Should().Throw(); - } + } - executed.Should().BeFalse(); - } + executed.Should().BeFalse(); + } - #endregion + #endregion - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() - { - var timeout = 10; - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - policy.Awaiting(p => p.ExecuteAsync( + var timeout = 10; + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Awaiting(p => p.ExecuteAsync( ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution return Task.FromResult(ResultPrimitive.WhateverButTooLate); }, userTokenSource.Token) // ... with user token. - ).Should().Throw(); + ).Should().Throw(); + } } - } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() - { - var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Awaiting(p => p.ExecuteAsync(async _ => + policy.Awaiting(p => p.ExecuteAsync(async _ => { executed = true; await TaskHelper.EmptyTask; return ResultPrimitive.WhateverButTooLate; }, cts.Token)) .Should().Throw(); - } - - executed.Should().BeFalse(); - } + } - #endregion + executed.Should().BeFalse(); + } - #region onTimeout overload - pessimistic + #endregion - [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + #region onTimeout overload - pessimistic - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Fact] + public void Should_call_ontimeout_with_configured_timeout__pessimistic() { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => + [Fact] + public void Should_call_ontimeout_with_passed_context__pessimistic() { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(p => p.ExecuteAsync(async _ => + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute)) - .Should().Throw(); + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + policy.Awaiting(p => p.ExecuteAsync(async _ => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute)) + .Should().Throw(); - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + var timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); - policy.Awaiting(p => p.ExecuteAsync(async () => + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().Throw(); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().Throw(); - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - policy.Awaiting(p => p.ExecuteAsync(async _ => + policy.Awaiting(p => p.ExecuteAsync(async _ => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; }, context)) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().Throw(); - taskPassedToOnTimeout.Should().NotBeNull(); - } + taskPassedToOnTimeout.Should().NotBeNull(); + } - [Fact] - public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. - // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + [Fact] + public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. + // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - Exception exceptionToThrow = new DivideByZeroException(); + Exception exceptionToThrow = new DivideByZeroException(); - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. - return TaskHelper.EmptyTask; - }; + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. + return TaskHelper.EmptyTask; + }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - policy.Awaiting(p => p.ExecuteAsync(async () => + policy.Awaiting(p => p.ExecuteAsync(async () => { await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); throw exceptionToThrow; })) .Should().Throw(); - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - - } + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + } - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - policy.Awaiting(p => p.ExecuteAsync(async () => + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().Throw(); + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(p => p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().Throw(); - #endregion + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #region onTimeout overload - optimistic + #endregion - [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + #region onTimeout overload - optimistic - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Fact] + public void Should_call_ontimeout_with_configured_timeout__optimistic() { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, _, _) => + [Fact] + public void Should_call_ontimeout_with_passed_context__optimistic() { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, _, _) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute, userCancellationToken)) - .Should().Throw(); + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { + policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute, userCancellationToken)) + .Should().Throw(); - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - policy.Awaiting(p => p.ExecuteAsync(async ct => + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().Throw(); + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().Throw(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (_, span, _) => + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (_, span, _) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => + policy.Awaiting(p => p.ExecuteAsync(async (_, ct) => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, context, userCancellationToken)) - .Should().Throw(); + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, task) => + [Fact] + public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, task) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; - policy.Awaiting(p => p.ExecuteAsync(async ct => + policy.Awaiting(p => p.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().Throw(); - taskPassedToOnTimeout.Should().BeNull(); - } - - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + taskPassedToOnTimeout.Should().BeNull(); + } - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (_, _, _, exception) => + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(p => p.ExecuteAsync(async ct => + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (_, _, _, exception) => { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().Throw(); + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(p => p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().Throw(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs index 025b422084f..3f77613bd18 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs @@ -8,815 +8,816 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Timeout; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class TimeoutTResultSpecs : TimeoutSpecsBase +namespace Polly.Specs.Timeout { - #region Configuration - - [Fact] - public void Should_throw_when_timeout_is_zero_by_timespan() + [Collection(Constants.SystemClockDependentTestCollection)] + public class TimeoutTResultSpecs : TimeoutSpecsBase { - Action policy = () => Policy.Timeout(TimeSpan.Zero); + #region Configuration - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } - - [Fact] - public void Should_throw_when_timeout_is_zero_by_seconds() - { - Action policy = () => Policy.Timeout(0); + [Fact] + public void Should_throw_when_timeout_is_zero_by_timespan() + { + Action policy = () => Policy.Timeout(TimeSpan.Zero); - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); + [Fact] + public void Should_throw_when_timeout_is_zero_by_seconds() + { + Action policy = () => Policy.Timeout(0); - policy.Should().Throw().And - .ParamName.Should().Be("timeout"); - } + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(-10); + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(-TimeSpan.FromHours(1)); - policy.Should().Throw().And - .ParamName.Should().Be("seconds"); - } + policy.Should().Throw().And + .ParamName.Should().Be("timeout"); + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() - { - Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); + [Fact] + public void Should_throw_when_timeout_is_less_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(-10); - policy.Should().NotThrow(); - } + policy.Should().Throw().And + .ParamName.Should().Be("seconds"); + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() - { - Action policy = () => Policy.Timeout(3); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_timespan() + { + Action policy = () => Policy.Timeout(TimeSpan.FromMilliseconds(1)); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_maxvalue() - { - Action policy = () => Policy.Timeout(TimeSpan.MaxValue); + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_zero_by_seconds() + { + Action policy = () => Policy.Timeout(3); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_seconds_is_maxvalue() - { - Action policy = () => Policy.Timeout(int.MaxValue); + [Fact] + public void Should_not_throw_when_timeout_is_maxvalue() + { + Action policy = () => Policy.Timeout(TimeSpan.MaxValue); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); + [Fact] + public void Should_not_throw_when_timeout_seconds_is_maxvalue() + { + Action policy = () => Policy.Timeout(int.MaxValue); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() - { - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy() + { + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() - { - Action doNothing = (_, _, _) => { }; - Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); - policy.Should().NotThrow(); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() + { + Action doNothing = (_, _, _) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timespan_and_onTimeout_is_full_argument_set() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_seconds_and_onTimeout_is_full_argument_set() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(30, onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - [Fact] - public void Should_throw_when_timeoutProvider_is_null() - { - Action policy = () => Policy.Timeout((Func)null); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("timeoutProvider"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + [Fact] + public void Should_throw_when_timeoutProvider_is_null() + { + Action policy = () => Policy.Timeout((Func)null); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("timeoutProvider"); + } - [Fact] - public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_and_onTimeout_is_full_argument_set() - { - Action onTimeout = null; - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - policy.Should().Throw() - .And.ParamName.Should().Be("onTimeout"); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - [Fact] - public void Should_be_able_to_configure_with_timeout_func() - { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); - policy.Should().NotThrow(); - } + policy.Should().Throw() + .And.ParamName.Should().Be("onTimeout"); + } - #endregion + [Fact] + public void Should_be_able_to_configure_with_timeout_func() + { + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(1)); - #region Timeout operation - pessimistic + policy.Should().NotThrow(); + } - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() - { - var timeout = TimeSpan.FromMilliseconds(50); + #endregion - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + #region Timeout operation - pessimistic - policy.Invoking(p => p.Execute(() => + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })).Should().Throw(); - } + var timeout = TimeSpan.FromMilliseconds(50); - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; - - var act = () => { - result = policy.Execute(ct => + policy.Invoking(p => p.Execute(() => { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })).Should().Throw(); + } - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_throw_timeout_after_correct_duration__pessimistic() - { - var watch = new Stopwatch(); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; + + var act = () => { + result = policy.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; + + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } + + [Fact] + public void Should_throw_timeout_after_correct_duration__pessimistic() + { + var watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Start(); - policy.Invoking(p => p.Execute(() => + watch.Start(); + policy.Invoking(p => p.Execute(() => { SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw(); - watch.Stop(); - - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } + .Should().Throw(); + watch.Stop(); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - [Fact] - public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + } - // Check to see if nested aggregate exceptions are unwrapped correctly - var exception = new AggregateException(msg, new NotImplementedException()); + [Fact] + public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic_with_full_stacktrace() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; - policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); return ResultPrimitive.WhateverButTooLate; })) - .Should().Throw() - .WithMessage(exception.Message) - .Where(e => e.InnerException is NotImplementedException) - .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); - } + // Check to see if nested aggregate exceptions are unwrapped correctly + var exception = new AggregateException(msg, new NotImplementedException()); - [Fact] - public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - var msg = "Aggregate Exception thrown from the delegate"; - - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - var aggregateException = new AggregateException(msg, innerException1, innerException2); - var func = () => { Helper_ThrowException(aggregateException); return ResultPrimitive.WhateverButTooLate; }; - - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - func.Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - - policy.Invoking(p => p.Execute(func)).Should().Throw() - .WithMessage(aggregateException.Message) - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); return ResultPrimitive.WhateverButTooLate; })) + .Should().Throw() + .WithMessage(exception.Message) + .Where(e => e.InnerException is NotImplementedException) + .And.StackTrace.Should().Contain(nameof(Helper_ThrowException)); + } - [Fact] - public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + [Fact] + public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_inside_delegate__pessimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + var msg = "Aggregate Exception thrown from the delegate"; + + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + var aggregateException = new AggregateException(msg, innerException1, innerException2); + var func = () => { Helper_ThrowException(aggregateException); return ResultPrimitive.WhateverButTooLate; }; + + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + func.Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + + policy.Invoking(p => p.Execute(func)).Should().Throw() + .WithMessage(aggregateException.Message) + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - var func = () => + [Fact] + public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() { - var task1 = Task.Run(() => throw innerException1); - var task2 = Task.Run(() => throw innerException2); - Task.WhenAll(task1, task2).Wait(); - return ResultPrimitive.WhateverButTooLate; - }; + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - func.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + var func = () => + { + var task1 = Task.Run(() => throw innerException1); + var task2 = Task.Run(() => throw innerException2); + Task.WhenAll(task1, task2).Wait(); + return ResultPrimitive.WhateverButTooLate; + }; - policy.Invoking(p => p.Execute(func)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + func.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - [Fact] - public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); + policy.Invoking(p => p.Execute(func)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - Exception innerException1 = new NotImplementedException(); - Exception innerException2 = new DivideByZeroException(); - var func = () => + [Fact] + public void Should_rethrow_aggregate_exception_with_another_example_cause_of_multiple_exceptions_from_inside_delegate__pessimistic() { - var action1 = () => { throw innerException1; }; - Action action2 = () => throw innerException2; - Parallel.Invoke(action1, action2); - return ResultPrimitive.WhateverButTooLate; - }; + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic); - // Whether executing the delegate directly, or through the policy, exception behavior should be the same. - func.Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + Exception innerException1 = new NotImplementedException(); + Exception innerException2 = new DivideByZeroException(); + var func = () => + { + var action1 = () => { throw innerException1; }; + Action action2 = () => throw innerException2; + Parallel.Invoke(action1, action2); + return ResultPrimitive.WhateverButTooLate; + }; - policy.Invoking(p => p.Execute(func)).Should().Throw() - .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - } + // Whether executing the delegate directly, or through the policy, exception behavior should be the same. + func.Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); - #endregion + policy.Invoking(p => p.Execute(func)).Should().Throw() + .And.InnerExceptions.Should().BeEquivalentTo(new[] { innerException1, innerException2 }); + } - #region Timeout operation - optimistic + #endregion - [Fact] - public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + #region Timeout operation - optimistic + + [Fact] + public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromMilliseconds(50), TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => + policy.Invoking(p => p.Execute(ct => { SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().Throw(); - } + } - [Fact] - public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - var result = ResultPrimitive.Undefined; - var userCancellationToken = CancellationToken.None; + [Fact] + public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); + var result = ResultPrimitive.Undefined; + var userCancellationToken = CancellationToken.None; - var act = () => { - result = policy.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); - return ResultPrimitive.Good; - }, userCancellationToken); - }; + var act = () => { + result = policy.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); + return ResultPrimitive.Good; + }, userCancellationToken); + }; - act.Should().NotThrow(); - result.Should().Be(ResultPrimitive.Good); - } + act.Should().NotThrow(); + result.Should().Be(ResultPrimitive.Good); + } - [Fact] - public void Should_throw_timeout_after_correct_duration__optimistic() - { - var watch = new Stopwatch(); + [Fact] + public void Should_throw_timeout_after_correct_duration__optimistic() + { + var watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromSeconds(1); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); + var userCancellationToken = CancellationToken.None; - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. - watch.Start(); - policy.Invoking(p => p.Execute(ct => + watch.Start(); + policy.Invoking(p => p.Execute(ct => { SystemClock.Sleep(TimeSpan.FromSeconds(10), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) - .Should().Throw(); - watch.Stop(); - - watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); - } + .Should().Throw(); + watch.Stop(); - [Fact] - public void Should_rethrow_exception_from_inside_delegate__optimistic() - { - var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); + watch.Elapsed.Should().BeCloseTo(timeout, ((int)tolerance.TotalMilliseconds)); + } - policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); - } + [Fact] + public void Should_rethrow_exception_from_inside_delegate__optimistic() + { + var policy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic); - #endregion + policy.Invoking(p => p.Execute(() => throw new NotImplementedException())).Should().Throw(); + } - #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) + #endregion - [Fact] - public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() - { - var timeout = 5; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + #region Non-timeout cancellation - pessimistic (user-delegate does not observe cancellation) - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { - policy.Invoking(p => p.Execute( - _ => { - userTokenSource.Cancel(); // User token cancels in the middle of execution ... - SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), - CancellationToken.None // ... but if the executed delegate does not observe it - ); - return ResultPrimitive.WhateverButTooLate; - }, userTokenSource.Token) - ).Should().Throw(); // ... it's still the timeout we expect. + var timeout = 5; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); + + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Invoking(p => p.Execute( + _ => { + userTokenSource.Cancel(); // User token cancels in the middle of execution ... + SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), + CancellationToken.None // ... but if the executed delegate does not observe it + ); + return ResultPrimitive.WhateverButTooLate; + }, userTokenSource.Token) + ).Should().Throw(); // ... it's still the timeout we expect. + } } - } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() - { - var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__pessimistic() + { + var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Invoking(p => p.Execute(_ => + policy.Invoking(p => p.Execute(_ => { executed = true; return ResultPrimitive.WhateverButTooLate; }, cts.Token)) .Should().Throw(); - } + } - executed.Should().BeFalse(); - } + executed.Should().BeFalse(); + } - #endregion + #endregion - #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) + #region Non-timeout cancellation - optimistic (user-delegate observes cancellation) - [Fact] - public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() - { - var timeout = 10; - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); - using (var userTokenSource = new CancellationTokenSource()) + [Fact] + public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - policy.Invoking(p => p.Execute( + var timeout = 10; + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); + using (var userTokenSource = new CancellationTokenSource()) + { + policy.Invoking(p => p.Execute( ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); // Simulate cancel in the middle of execution return ResultPrimitive.WhateverButTooLate; }, userTokenSource.Token) // ... with user token. - ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. + ).Should().Throw(); // Not a TimeoutRejectedException; i.e. policy can distinguish user cancellation from timeout cancellation. + } } - } - [Fact] - public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() - { - var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); + [Fact] + public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled_before_delegate_reached__optimistic() + { + var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); - var executed = false; + var executed = false; - using (var cts = new CancellationTokenSource()) - { - cts.Cancel(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); - policy.Invoking(p => p.Execute(_ => + policy.Invoking(p => p.Execute(_ => { executed = true; return ResultPrimitive.WhateverButTooLate; }, cts.Token)) .Should().Throw(); - } + } - executed.Should().BeFalse(); - } + executed.Should().BeFalse(); + } - #endregion + #endregion - #region onTimeout overload - pessimistic + #region onTimeout overload - pessimistic - [Fact] - public void Should_call_ontimeout_with_configured_timeout__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_configured_timeout__pessimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => + policy.Invoking(p => p.Execute(() => { SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - [Fact] - public void Should_call_ontimeout_with_passed_context__pessimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + [Fact] + public void Should_call_ontimeout_with_passed_context__pessimistic() + { + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(_ => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute)) - .Should().Throw(); + policy.Invoking(p => p.Execute(_ => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute)) + .Should().Throw(); - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().Throw(); + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - policy.Invoking(p => p.Execute(_ => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, context)) - .Should().Throw(); + policy.Invoking(p => p.Execute(_ => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, context)) + .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => + policy.Invoking(p => p.Execute(() => { SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); return ResultPrimitive.WhateverButTooLate; })) .Should().Throw(); - taskPassedToOnTimeout.Should().NotBeNull(); - } + taskPassedToOnTimeout.Should().NotBeNull(); + } - [Fact] - public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. - // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + [Fact] + public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. + // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - Exception exceptionToThrow = new DivideByZeroException(); + Exception exceptionToThrow = new DivideByZeroException(); - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); - }; + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); + }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); + var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => + policy.Invoking(p => p.Execute(() => { SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); throw exceptionToThrow; })) .Should().Throw(); - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - } + } - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__pessimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .Should().Throw(); + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .Should().Throw(); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion - #region onTimeout overload - optimistic + #region onTimeout overload - optimistic - [Fact] - public void Should_call_ontimeout_with_configured_timeout__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_configured_timeout__optimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => + policy.Invoking(p => p.Execute(ct => { SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().Throw(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } - [Fact] - public void Should_call_ontimeout_with_passed_context__optimistic() - { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + [Fact] + public void Should_call_ontimeout_with_passed_context__optimistic() + { + var operationKey = "SomeKey"; + var contextPassedToExecute = new Context(operationKey); - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute((_, ct) => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute, userCancellationToken)) - .Should().Throw(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + policy.Invoking(p => p.Execute((_, ct) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute, userCancellationToken)) + .Should().Throw(); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().Throw(); + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + policy.Invoking(p => p.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().Throw(); - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } - // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute((_, ct) => + // Supply a programatically-controlled timeout, via the execution context. + var context = new Context("SomeOperationKey") { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, context, userCancellationToken)) - .Should().Throw(); + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } + policy.Invoking(p => p.Execute((_, ct) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, context, userCancellationToken)) + .Should().Throw(); - [Fact] - public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => + policy.Invoking(p => p.Execute(ct => { SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); return ResultPrimitive.WhateverButTooLate; }, userCancellationToken)) .Should().Throw(); - taskPassedToOnTimeout.Should().BeNull(); - } + taskPassedToOnTimeout.Should().BeNull(); + } - [Fact] - public void Should_call_ontimeout_with_timing_out_exception__optimistic() - { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .Should().Throw(); + policy.Invoking(p => p.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .Should().Throw(); - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs b/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs index 923c09e6ba3..75bd867ad0c 100644 --- a/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs +++ b/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs @@ -8,263 +8,264 @@ using Polly.NoOp; using Polly.Retry; -namespace Polly.Specs.Wrap; - -public class IPolicyWrapExtensionSpecs +namespace Polly.Specs.Wrap { - - [Fact] - public void Should_pass_all_nested_policies_from_PolicyWrap_in_same_order_they_were_added() - { - var policy0 = Policy.NoOp(); - var policy1 = Policy.NoOp(); - var policy2 = Policy.NoOp(); - - var policyWrap = Policy.Wrap(policy0, policy1, policy2); - - var policies = policyWrap.GetPolicies().ToList(); - policies.Count.Should().Be(3); - policies[0].Should().Be(policy0); - policies[1].Should().Be(policy1); - policies[2].Should().Be(policy2); - } - - [Fact] - public void Should_return_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - var wrap = Policy.Wrap(policyA, policyB); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void Threepolicies_by_static_sequence_should_return_correct_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - Policy policyC = Policy.NoOp(); - var wrap = Policy.Wrap(policyA, policyB, policyC); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void Threepolicies_lefttree_should_return_correct_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB).Wrap(policyC); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void Threepolicies_righttree_should_return_correct_sequence_from_GetPolicies() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_single_policy_of_type_TPolicy() + public class IPolicyWrapExtensionSpecs { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyB }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_empty_enumerable_if_no_policy_of_type_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEmpty(); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_multiple_policies_of_type_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_policies_of_type_TPolicy_matching_predicate() - { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - policyA.Isolate(); - - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies(p => p.CircuitState == CircuitState.Closed).Should().BeEquivalentTo(new[] { policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_should_return_empty_enumerable_if_none_match_predicate() - { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies(p => p.CircuitState == CircuitState.Open).Should().BeEmpty(); - } - [Fact] - public void GetPoliciesTPolicy_with_predicate_should_return_multiple_policies_of_type_TPolicy_if_multiple_match_predicate() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicies(_ => true).Should().BeEquivalentTo(new[] { policyA, policyC }, - options => options - .WithStrictOrdering() - .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) - .WhenTypeIs()); - } - - [Fact] - public void GetPoliciesTPolicy_with_predicate_should_throw_if_predicate_is_null() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); - - Action configure = () => wrap.GetPolicies(null); + [Fact] + public void Should_pass_all_nested_policies_from_PolicyWrap_in_same_order_they_were_added() + { + var policy0 = Policy.NoOp(); + var policy1 = Policy.NoOp(); + var policy2 = Policy.NoOp(); + + var policyWrap = Policy.Wrap(policy0, policy1, policy2); + + var policies = policyWrap.GetPolicies().ToList(); + policies.Count.Should().Be(3); + policies[0].Should().Be(policy0); + policies[1].Should().Be(policy1); + policies[2].Should().Be(policy2); + } + + [Fact] + public void Should_return_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + var wrap = Policy.Wrap(policyA, policyB); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void Threepolicies_by_static_sequence_should_return_correct_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + Policy policyC = Policy.NoOp(); + var wrap = Policy.Wrap(policyA, policyB, policyC); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void Threepolicies_lefttree_should_return_correct_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB).Wrap(policyC); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void Threepolicies_righttree_should_return_correct_sequence_from_GetPolicies() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_single_policy_of_type_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyB }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_empty_enumerable_if_no_policy_of_type_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEmpty(); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_multiple_policies_of_type_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_policies_of_type_TPolicy_matching_predicate() + { + var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + policyA.Isolate(); + + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies(p => p.CircuitState == CircuitState.Closed).Should().BeEquivalentTo(new[] { policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_should_return_empty_enumerable_if_none_match_predicate() + { + var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies(p => p.CircuitState == CircuitState.Open).Should().BeEmpty(); + } + + [Fact] + public void GetPoliciesTPolicy_with_predicate_should_return_multiple_policies_of_type_TPolicy_if_multiple_match_predicate() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicies(_ => true).Should().BeEquivalentTo(new[] { policyA, policyC }, + options => options + .WithStrictOrdering() + .Using(ctx => ctx.Subject.Should().BeSameAs(ctx.Expectation)) + .WhenTypeIs()); + } + + [Fact] + public void GetPoliciesTPolicy_with_predicate_should_throw_if_predicate_is_null() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + var wrap = policyA.Wrap(policyB); + + Action configure = () => wrap.GetPolicies(null); - configure.Should().Throw().And.ParamName.Should().Be("filter"); - } - - [Fact] - public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy().Should().BeSameAs(policyB); - } - - [Fact] - public void GetPolicyTPolicy_should_return_null_if_no_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy().Should().BeNull(); - } - - [Fact] - public void GetPolicyTPolicy_should_throw_if_multiple_TPolicy() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.Invoking(p => p.GetPolicy()).Should().Throw(); - } - - [Fact] - public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy_matching_predicate() - { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - policyA.Isolate(); - - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy(p => p.CircuitState == CircuitState.Closed).Should().BeSameAs(policyC); - } - - [Fact] - public void GetPolicyTPolicy_should_return_null_if_none_match_predicate() - { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.GetPolicy(p => p.CircuitState == CircuitState.Open).Should().BeNull(); - } - - [Fact] - public void GetPolicyTPolicy_with_predicate_should_throw_if_multiple_TPolicy_if_multiple_match_predicate() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.Handle().Retry(); - Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); - - wrap.Invoking(p => p.GetPolicy(_ => true)).Should().Throw(); - } - - [Fact] - public void GetPolicyTPolicy_with_predicate_should_throw_if_predicate_is_null() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); - - Action configure = () => wrap.GetPolicy(null); - - configure.Should().Throw().And.ParamName.Should().Be("filter"); + configure.Should().Throw().And.ParamName.Should().Be("filter"); + } + + [Fact] + public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy().Should().BeSameAs(policyB); + } + + [Fact] + public void GetPolicyTPolicy_should_return_null_if_no_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy().Should().BeNull(); + } + + [Fact] + public void GetPolicyTPolicy_should_throw_if_multiple_TPolicy() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.Invoking(p => p.GetPolicy()).Should().Throw(); + } + + [Fact] + public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy_matching_predicate() + { + var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + policyA.Isolate(); + + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy(p => p.CircuitState == CircuitState.Closed).Should().BeSameAs(policyC); + } + + [Fact] + public void GetPolicyTPolicy_should_return_null_if_none_match_predicate() + { + var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + Policy policyB = Policy.Handle().Retry(); + var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.GetPolicy(p => p.CircuitState == CircuitState.Open).Should().BeNull(); + } + + [Fact] + public void GetPolicyTPolicy_with_predicate_should_throw_if_multiple_TPolicy_if_multiple_match_predicate() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.Handle().Retry(); + Policy policyC = Policy.NoOp(); + var wrap = policyA.Wrap(policyB.Wrap(policyC)); + + wrap.Invoking(p => p.GetPolicy(_ => true)).Should().Throw(); + } + + [Fact] + public void GetPolicyTPolicy_with_predicate_should_throw_if_predicate_is_null() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + var wrap = policyA.Wrap(policyB); + + Action configure = () => wrap.GetPolicy(null); + + configure.Should().Throw().And.ParamName.Should().Be("filter"); + } } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs index f973709918f..8f9315855b9 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs @@ -3,278 +3,280 @@ using Polly.Specs.Helpers; using Xunit; -namespace Polly.Specs.Wrap; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class PolicyWrapContextAndKeySpecs +namespace Polly.Specs.Wrap { - #region PolicyKey and execution Context tests - - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + [Collection(Constants.SystemClockDependentTestCollection)] + public class PolicyWrapContextAndKeySpecs { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + #region PolicyKey and execution Context tests - string policyWrapKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + string policyWrapKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - wrap.RaiseException(1); + var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + wrap.RaiseException(1); - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => {}; - - var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - wrap.RaiseException(1); - - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } - - [Fact] - public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - ISyncPolicy fallback = Policy - .Handle() - .Fallback(_ => {}, onFallback: (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - }) - .WithPolicyKey("FallbackPolicy"); - - ISyncPolicy retry = Policy - .Handle() - .Retry(1, onRetry: (_, _, context) => + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => {}; - ISyncPolicy policyWrap = Policy.Wrap(fallback, retry) - .WithPolicyKey("PolicyWrap"); + var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - policyWrap.Execute(() => throw new Exception()); - } + wrap.RaiseException(1); - [Fact] - public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } + + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + ISyncPolicy fallback = Policy + .Handle() + .Fallback(_ => {}, onFallback: (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + }) + .WithPolicyKey("FallbackPolicy"); + + ISyncPolicy retry = Policy + .Handle() + .Retry(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + ISyncPolicy policyWrap = Policy.Wrap(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.Execute(() => throw new Exception()); + } + + [Fact] + public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() + { + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var fallbackKey = Guid.NewGuid().ToString(); + var innerWrapKey = Guid.NewGuid().ToString(); + var outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); - var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); - outerWrap.RaiseException(1); + outerWrap.RaiseException(1); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } - [Fact] - public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + [Fact] + public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var fallbackKey = Guid.NewGuid().ToString(); + var innerWrapKey = Guid.NewGuid().ToString(); + var outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().Fallback(() => { }).WithPolicyKey(fallbackKey); - var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); - var doneOnceOnly = false; - outerWrap.Execute(() => - { - if (!doneOnceOnly) + var doneOnceOnly = false; + outerWrap.Execute(() => { - doneOnceOnly = true; - throw new Exception(); - } - }); - - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + if (!doneOnceOnly) + { + doneOnceOnly = true; + throw new Exception(); + } + }); + + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } + + #endregion - #endregion - -} - -[Collection(Constants.SystemClockDependentTestCollection)] -public class PolicyWrapTResultContextAndKeySpecs -{ - #region PolicyKey and execution Context tests + } - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + [Collection(Constants.SystemClockDependentTestCollection)] + public class PolicyWrapTResultContextAndKeySpecs { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + #region PolicyKey and execution Context tests - string policyWrapKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + string policyWrapKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - [Fact] - public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => + [Fact] + public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => { }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => { }; - wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.Wrap(breaker).WithPolicyKey(wrapKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + wrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - [Fact] - public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - ISyncPolicy fallback = Policy - .Handle() - .Fallback(ResultPrimitive.Undefined, onFallback: (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - }) - .WithPolicyKey("FallbackPolicy"); - - ISyncPolicy retry = Policy - .Handle() - .Retry(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - var policyWrap = Policy.Wrap(fallback, retry) - .WithPolicyKey("PolicyWrap"); + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + ISyncPolicy fallback = Policy + .Handle() + .Fallback(ResultPrimitive.Undefined, onFallback: (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + }) + .WithPolicyKey("FallbackPolicy"); + + ISyncPolicy retry = Policy + .Handle() + .Retry(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + var policyWrap = Policy.Wrap(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.Execute(() => throw new Exception()); + } + + [Fact] + public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() + { + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var fallbackKey = Guid.NewGuid().ToString(); + var innerWrapKey = Guid.NewGuid().ToString(); + var outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - policyWrap.Execute(() => throw new Exception()); - } + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.HandleResult(ResultPrimitive.Fault).Fallback(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); - [Fact] - public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => - { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.HandleResult(ResultPrimitive.Fault).Fallback(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); + outerWrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); - var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } - outerWrap.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + #endregion - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); } - #endregion - -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs index 9ebb3766f77..dc031e31a06 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs @@ -5,344 +5,346 @@ using Polly.Utilities; using Xunit; -namespace Polly.Specs.Wrap; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class PolicyWrapContextAndKeySpecsAsync +namespace Polly.Specs.Wrap { - #region PolicyKey and execution Context tests - - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + [Collection(Constants.SystemClockDependentTestCollection)] + public class PolicyWrapContextAndKeySpecsAsync { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + #region PolicyKey and execution Context tests - string policyWrapKeySetOnExecutionContext = null; - Action onRetry = (_, _, context) => + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + string policyWrapKeySetOnExecutionContext = null; + Action onRetry = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - await wrap.RaiseExceptionAsync(1); + var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + await wrap.RaiseExceptionAsync(1); - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => { }; - - var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - - await wrap.RaiseExceptionAsync(1); - - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } - - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); - - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - await policyWrap.ExecuteAsync(() => throw new Exception()); - } - - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => { }; - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); + var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); + await wrap.RaiseExceptionAsync(1); - await policyWrap.ExecuteAsync(async () => await Task.Run(() => throw new Exception())); // Regression test for issue 510 - } + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - [Fact] - public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + await policyWrap.ExecuteAsync(() => throw new Exception()); + } + + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => TaskHelper.EmptyTask, (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + await policyWrap.ExecuteAsync(async () => await Task.Run(() => throw new Exception())); // Regression test for issue 510 + } + + [Fact] + public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() + { + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var fallbackKey = Guid.NewGuid().ToString(); + var innerWrapKey = Guid.NewGuid().ToString(); + var outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); - var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); - await outerWrap.RaiseExceptionAsync(1); + await outerWrap.RaiseExceptionAsync(1); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } - [Fact] - public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action onBreak = (_, _, context) => + [Fact] + public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var fallbackKey = Guid.NewGuid().ToString(); + var innerWrapKey = Guid.NewGuid().ToString(); + var outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); + var retry = Policy.Handle().RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.Handle().FallbackAsync(_ => TaskHelper.EmptyTask).WithPolicyKey(fallbackKey); - var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); - var doneOnceOnly = false; - await outerWrap.ExecuteAsync(() => - { - if (!doneOnceOnly) + var doneOnceOnly = false; + await outerWrap.ExecuteAsync(() => { - doneOnceOnly = true; - throw new Exception(); - } - return TaskHelper.EmptyTask; - }); - - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + if (!doneOnceOnly) + { + doneOnceOnly = true; + throw new Exception(); + } + return TaskHelper.EmptyTask; + }); - #endregion + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } -} + #endregion -[Collection(Constants.SystemClockDependentTestCollection)] -public class PolicyWrapTResultContextAndKeySpecsAsync -{ - #region PolicyKey and execution Context tests + } - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() + [Collection(Constants.SystemClockDependentTestCollection)] + public class PolicyWrapTResultContextAndKeySpecsAsync { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + #region PolicyKey and execution Context tests - string policyWrapKeySetOnExecutionContext = null; - Action, int, Context> onRetry = (_, _, context) => + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + string policyWrapKeySetOnExecutionContext = null; + Action, int, Context> onRetry = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; - await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } + await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - [Fact] - public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => + [Fact] + public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action onReset = _ => { }; - - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); - var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var wrapKey = Guid.NewGuid().ToString(); - await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); - } - - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); - - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); - - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action onReset = _ => { }; - await policyWrap.ExecuteAsync(() => throw new Exception()); - } - - [Fact] - public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() - { - IAsyncPolicy fallback = Policy - .Handle() - .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("FallbackPolicy"); - return TaskHelper.EmptyTask; - }) - .WithPolicyKey("FallbackPolicy"); + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, onReset).WithPolicyKey(breakerKey); + var wrap = retry.WrapAsync(breaker).WithPolicyKey(wrapKey); - IAsyncPolicy retry = Policy - .Handle() - .RetryAsync(1, onRetry: (_, _, context) => - { - context.PolicyWrapKey.Should().Be("PolicyWrap"); - context.PolicyKey.Should().Be("RetryPolicy"); - }) - .WithPolicyKey("RetryPolicy"); + await wrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) - .WithPolicyKey("PolicyWrap"); + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); + } - await policyWrap.ExecuteAsync(async () => await Task.Run(() => // Regression test for issue 510 + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() { - throw new Exception(); + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + await policyWrap.ExecuteAsync(() => throw new Exception()); + } + + [Fact] + public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap_with_deeper_async_execution() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, _) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (_, _, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + await policyWrap.ExecuteAsync(async () => await Task.Run(() => // Regression test for issue 510 + { + throw new Exception(); #pragma warning disable 0162 // unreachable code detected - return ResultPrimitive.WhateverButTooLate; + return ResultPrimitive.WhateverButTooLate; #pragma warning restore 0162 - })); - } + })); + } - [Fact] - public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() - { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); - - string policyWrapKeySetOnExecutionContext = null; - Action, TimeSpan, Context> onBreak = (_, _, context) => + [Fact] + public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() { - policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; - }; - Action doNothingOnReset = _ => { }; + var retryKey = Guid.NewGuid().ToString(); + var breakerKey = Guid.NewGuid().ToString(); + var fallbackKey = Guid.NewGuid().ToString(); + var innerWrapKey = Guid.NewGuid().ToString(); + var outerWrapKey = Guid.NewGuid().ToString(); + + string policyWrapKeySetOnExecutionContext = null; + Action, TimeSpan, Context> onBreak = (_, _, context) => + { + policyWrapKeySetOnExecutionContext = context.PolicyWrapKey; + }; + Action doNothingOnReset = _ => { }; - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); - var fallback = Policy.HandleResult(ResultPrimitive.Fault).FallbackAsync(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1).WithPolicyKey(retryKey); + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(1, TimeSpan.Zero, onBreak, doNothingOnReset).WithPolicyKey(breakerKey); + var fallback = Policy.HandleResult(ResultPrimitive.Fault).FallbackAsync(ResultPrimitive.Substitute).WithPolicyKey(fallbackKey); - var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); - var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); + var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); + var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); - await outerWrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + await outerWrap.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); - policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); - policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); - policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); - } + policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(breakerKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(fallbackKey); + policyWrapKeySetOnExecutionContext.Should().NotBe(innerWrapKey); + policyWrapKeySetOnExecutionContext.Should().Be(outerWrapKey); + } + + #endregion - #endregion + } -} \ No newline at end of file +} diff --git a/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs b/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs index 2d786cd0e88..8143b5d62fc 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs @@ -6,569 +6,570 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Wrap; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class PolicyWrapSpecs +namespace Polly.Specs.Wrap { - #region Instance configuration syntax tests, non-generic outer - - [Fact] - public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + [Collection(Constants.SystemClockDependentTestCollection)] + public class PolicyWrapSpecs { - var retry = Policy.Handle().Retry(1); + #region Instance configuration syntax tests, non-generic outer - Action config = () => retry.Wrap((Policy)null); + [Fact] + public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.Handle().Retry(1); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + Action config = () => retry.Wrap((Policy)null); - [Fact] - public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.Handle().Retry(1); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - Action config = () => retry.Wrap((Policy)null); + [Fact] + public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.Handle().Retry(1); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + Action config = () => retry.Wrap((Policy)null); - [Fact] - public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - var wrap = policyA.Wrap(policyB); + [Fact] + public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + var wrap = policyA.Wrap(policyB); - [Fact] - public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - var wrap = policyA.Wrap(policyB); + [Fact] + public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + var wrap = policyA.Wrap(policyB); - #endregion + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - #region Instance configuration syntax tests, generic outer + #endregion - [Fact] - public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.HandleResult(0).Retry(1); + #region Instance configuration syntax tests, generic outer - Action config = () => retry.Wrap((Policy)null); + [Fact] + public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.HandleResult(0).Retry(1); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + Action config = () => retry.Wrap((Policy)null); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.HandleResult(0).Retry(1); - Action config = () => retry.Wrap((Policy)null); + [Fact] + public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.HandleResult(0).Retry(1); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + Action config = () => retry.Wrap((Policy)null); - [Fact] - public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - var wrap = policyA.Wrap(policyB); + [Fact] + public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + var wrap = policyA.Wrap(policyB); - [Fact] - public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - var wrap = policyA.Wrap(policyB); + [Fact] + public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + var wrap = policyA.Wrap(policyB); - #endregion + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - #region Interface extension configuration syntax tests, non-generic outer + #endregion - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.Handle().Retry(1); + #region Interface extension configuration syntax tests, non-generic outer - Action config = () => outerNull.Wrap(retry); + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.Handle().Retry(1); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + Action config = () => outerNull.Wrap(retry); - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - Action config = () => outerNull.Wrap(retry); + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + Action config = () => outerNull.Wrap(retry); - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.Handle().Retry(1); - - Action config = () => retry.Wrap((Policy)null); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.Handle().Retry(1); - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.Handle().Retry(1); + Action config = () => retry.Wrap((Policy)null); - Action config = () => retry.Wrap((Policy)null); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.Handle().Retry(1); - [Fact] - public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + Action config = () => retry.Wrap((Policy)null); - IPolicyWrap wrap = policyA.Wrap(policyB); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); - [Fact] - public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + IPolicyWrap wrap = policyA.Wrap(policyB); - IPolicyWrap wrap = policyA.Wrap(policyB); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); - #endregion + IPolicyWrap wrap = policyA.Wrap(policyB); - #region Interface extension configuration syntax tests, generic outer + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.Handle().Retry(1); + #endregion - Action config = () => outerNull.Wrap(retry); + #region Interface extension configuration syntax tests, generic outer - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.Handle().Retry(1); - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - ISyncPolicy outerNull = null; - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + Action config = () => outerNull.Wrap(retry); - Action config = () => outerNull.Wrap(retry); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + ISyncPolicy outerNull = null; + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + Action config = () => outerNull.Wrap(retry); - Action config = () => retry.Wrap((Policy)null); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - ISyncPolicy retry = Policy.HandleResult(0).Retry(1); + Action config = () => retry.Wrap((Policy)null); - Action config = () => retry.Wrap((Policy)null); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + ISyncPolicy retry = Policy.HandleResult(0).Retry(1); - [Fact] - public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + Action config = () => retry.Wrap((Policy)null); - IPolicyWrap wrap = policyA.Wrap(policyB); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); - [Fact] - public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - ISyncPolicy policyA = Policy.NoOp(); - ISyncPolicy policyB = Policy.NoOp(); + IPolicyWrap wrap = policyA.Wrap(policyB); - IPolicyWrap wrap = policyA.Wrap(policyB); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + ISyncPolicy policyA = Policy.NoOp(); + ISyncPolicy policyB = Policy.NoOp(); - #endregion + IPolicyWrap wrap = policyA.Wrap(policyB); - #region Static configuration syntax tests, non-generic policies + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Wrapping_nothing_using_static_wrap_syntax_should_throw() - { - Action config = () => Policy.Wrap(); + #endregion - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + #region Static configuration syntax tests, non-generic policies - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() - { - Policy singlePolicy = Policy.Handle().Retry(); - Action config = () => Policy.Wrap(new[] {singlePolicy}); + [Fact] + public void Wrapping_nothing_using_static_wrap_syntax_should_throw() + { + Action config = () => Policy.Wrap(); - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - [Fact] - public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.Wrap(new[] {retry, breaker}); + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() + { + Policy singlePolicy = Policy.Handle().Retry(); + Action config = () => Policy.Wrap(new[] {singlePolicy}); - config.Should().NotThrow(); - } + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(1); - Policy divideByZeroRetry = Policy.Handle().Retry(2); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); + [Fact] + public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.Wrap(new[] {retry, breaker}); - Action config = () => Policy.Wrap(new[] {divideByZeroRetry, retry, breaker}); + config.Should().NotThrow(); + } - config.Should().NotThrow(); - } + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(1); + Policy divideByZeroRetry = Policy.Handle().Retry(2); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - [Fact] - public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); + Action config = () => Policy.Wrap(new[] {divideByZeroRetry, retry, breaker}); - var wrap = Policy.Wrap(policyA, policyB); + config.Should().NotThrow(); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); - #endregion + var wrap = Policy.Wrap(policyA, policyB); - #region Static configuration syntax tests, strongly-typed policies + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() - { - Action config = () => Policy.Wrap(); + #endregion + + #region Static configuration syntax tests, strongly-typed policies - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + [Fact] + public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() + { + Action config = () => Policy.Wrap(); - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() - { - Policy singlePolicy = Policy.Handle().Retry(); - Action config = () => Policy.Wrap(new[] { singlePolicy }); - - config.Should().Throw().And.ParamName.Should().Be("policies"); - } - - [Fact] - public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.Wrap(new[] { retry, breaker }); - - config.Should().NotThrow(); - } - - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - Policy retry = Policy.Handle().Retry(); - Policy divideByZeroRetry = Policy.Handle().Retry(2); - Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); - - Action config = () => Policy.Wrap(new[] { divideByZeroRetry, retry, breaker }); - - config.Should().NotThrow(); - } - - [Fact] - public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() - { - Policy policyA = Policy.NoOp(); - Policy policyB = Policy.NoOp(); - - var wrap = Policy.Wrap(policyA, policyB); - - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } - - #endregion - - #region Instance-configured: execution tests - - [Fact] - public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = retry.Wrap(breaker); - var breakerWrappingRetry = breaker.Wrap(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = retry.Wrap(breaker); - var breakerWrappingRetry = breaker.Wrap(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region Static-configured: execution tests - - [Fact] - public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = Policy.Wrap(retry, breaker); - var breakerWrappingRetry = Policy.Wrap(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.Invoking(x => x.RaiseException(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - [Fact] - public void Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = Policy.Wrap(retry, breaker); - var breakerWrappingRetry = Policy.Wrap(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } - - #endregion - - #region ExecuteAndCaptureSpecs - - [Fact] - public void Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - } - - [Fact] - public void Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - } - - [Fact] - public void Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); - } - - [Fact] - public void Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); - } - - [Fact] - public void Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() - { - var innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); - - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.Fault); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } - - [Fact] - public void Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() - { - var innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); - - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.FaultAgain); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } + config.Should().Throw().And.ParamName.Should().Be("policies"); + } + + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() + { + Policy singlePolicy = Policy.Handle().Retry(); + Action config = () => Policy.Wrap(new[] { singlePolicy }); - #endregion -} \ No newline at end of file + config.Should().Throw().And.ParamName.Should().Be("policies"); + } + + [Fact] + public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.Wrap(new[] { retry, breaker }); + + config.Should().NotThrow(); + } + + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + Policy retry = Policy.Handle().Retry(); + Policy divideByZeroRetry = Policy.Handle().Retry(2); + Policy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.FromSeconds(10)); + + Action config = () => Policy.Wrap(new[] { divideByZeroRetry, retry, breaker }); + + config.Should().NotThrow(); + } + + [Fact] + public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() + { + Policy policyA = Policy.NoOp(); + Policy policyB = Policy.NoOp(); + + var wrap = Policy.Wrap(policyA, policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } + + #endregion + + #region Instance-configured: execution tests + + [Fact] + public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = retry.Wrap(breaker); + var breakerWrappingRetry = breaker.Wrap(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public void Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = retry.Wrap(breaker); + var breakerWrappingRetry = breaker.Wrap(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #region Static-configured: execution tests + + [Fact] + public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = Policy.Wrap(retry, breaker); + var breakerWrappingRetry = Policy.Wrap(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.Invoking(x => x.RaiseException(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public void Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = Policy.Wrap(retry, breaker); + var breakerWrappingRetry = Policy.Wrap(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #region ExecuteAndCaptureSpecs + + [Fact] + public void Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + } + + [Fact] + public void Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + } + + [Fact] + public void Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); + } + + [Fact] + public void Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreaker(1, TimeSpan.Zero); + var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); + } + + [Fact] + public void Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() + { + var innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(1, TimeSpan.Zero); + var outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.Zero); + var wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); + + var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.Fault); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); + } + + [Fact] + public void Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() + { + var innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreaker(1, TimeSpan.Zero); + var outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreaker(1, TimeSpan.Zero); + var wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); + + var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.FaultAgain); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); + } + + #endregion + } +} diff --git a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs index 11f6d02b75d..8ebcc74827d 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs @@ -6,571 +6,572 @@ using Polly.Wrap; using Xunit; -namespace Polly.Specs.Wrap; - -[Collection(Constants.SystemClockDependentTestCollection)] -public class PolicyWrapSpecsAsync +namespace Polly.Specs.Wrap { - #region Instance configuration syntax tests, non-generic outer - - [Fact] - public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.Handle().RetryAsync(1); - - Action config = () => retry.WrapAsync((AsyncPolicy)null); - - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } - - [Fact] - public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.Handle().RetryAsync(1); - - Action config = () => retry.WrapAsync((AsyncPolicy)null); - - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } - - [Fact] - public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); - - var wrap = policyA.WrapAsync(policyB); - - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } - - [Fact] - public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); - - var wrap = policyA.WrapAsync(policyB); - - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } - - #endregion - - #region Instance configuration syntax tests, generic outer - - [Fact] - public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - var retry = Policy.HandleResult(0).RetryAsync(1); - - Action config = () => retry.WrapAsync((AsyncPolicy)null); - - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } - - [Fact] - public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() + [Collection(Constants.SystemClockDependentTestCollection)] + public class PolicyWrapSpecsAsync { - var retry = Policy.HandleResult(0).RetryAsync(1); + #region Instance configuration syntax tests, non-generic outer - Action config = () => retry.WrapAsync((AsyncPolicy)null); + [Fact] + public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.Handle().RetryAsync(1); - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + Action config = () => retry.WrapAsync((AsyncPolicy)null); - [Fact] - public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); - - var wrap = policyA.WrapAsync(policyB); - - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + [Fact] + public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.Handle().RetryAsync(1); - var wrap = policyA.WrapAsync(policyB); + Action config = () => retry.WrapAsync((AsyncPolicy)null); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - #endregion + [Fact] + public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - #region Interface extension configuration syntax tests, non-generic outer + var wrap = policyA.WrapAsync(policyB); - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - Action config = () => outerNull.WrapAsync(retry); + [Fact] + public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + var wrap = policyA.WrapAsync(policyB); - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - Action config = () => outerNull.WrapAsync(retry); + #endregion - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + #region Instance configuration syntax tests, generic outer - [Fact] - public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - - Action config = () => retry.WrapAsync((AsyncPolicy)null); - - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } - - [Fact] - public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - - Action config = () => retry.WrapAsync((AsyncPolicy)null); - - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } - - [Fact] - public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); - - IPolicyWrap wrap = policyA.WrapAsync(policyB); - - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.HandleResult(0).RetryAsync(1); - [Fact] - public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); + Action config = () => retry.WrapAsync((AsyncPolicy)null); - IPolicyWrap wrap = policyA.WrapAsync(policyB); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + var retry = Policy.HandleResult(0).RetryAsync(1); - #endregion + Action config = () => retry.WrapAsync((AsyncPolicy)null); - #region Interface extension configuration syntax tests, generic outer + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.Handle().RetryAsync(1); + [Fact] + public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - Action config = () => outerNull.WrapAsync(retry); + var wrap = policyA.WrapAsync(policyB); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() - { - IAsyncPolicy outerNull = null; - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + [Fact] + public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - Action config = () => outerNull.WrapAsync(retry); + var wrap = policyA.WrapAsync(policyB); - config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + #endregion - Action config = () => retry.WrapAsync((AsyncPolicy)null); + #region Interface extension configuration syntax tests, non-generic outer - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - [Fact] - public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() - { - IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); + Action config = () => outerNull.WrapAsync(retry); - Action config = () => retry.WrapAsync((AsyncPolicy)null); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); - } + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); - [Fact] - public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); + Action config = () => outerNull.WrapAsync(retry); - IPolicyWrap wrap = policyA.WrapAsync(policyB); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Nongeneric_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - [Fact] - public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() - { - IAsyncPolicy policyA = Policy.NoOpAsync(); - IAsyncPolicy policyB = Policy.NoOpAsync(); + Action config = () => retry.WrapAsync((AsyncPolicy)null); - IPolicyWrap wrap = policyA.WrapAsync(policyB); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Nongeneric_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - #endregion + Action config = () => retry.WrapAsync((AsyncPolicy)null); - #region Static configuration syntax tests, non-generic policies + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Wrapping_nothing_using_static_wrap_syntax_should_throw() - { - Action config = () => Policy.WrapAsync(); + [Fact] + public void Nongeneric_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + IPolicyWrap wrap = policyA.WrapAsync(policyB); - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() - { - AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); - Action config = () => Policy.WrapAsync(new[] { singlePolicy }); + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + [Fact] + public void Nongeneric_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); - [Fact] - public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.WrapAsync(new[] { retry, breaker }); + IPolicyWrap wrap = policyA.WrapAsync(policyB); - config.Should().NotThrow(); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(1); - AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + #endregion - Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); + #region Interface extension configuration syntax tests, generic outer - config.Should().NotThrow(); - } + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.Handle().RetryAsync(1); - [Fact] - public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + Action config = () => outerNull.WrapAsync(retry); - var wrap = Policy.WrapAsync(policyA, policyB); + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_null_wrapping_should_throw() + { + IAsyncPolicy outerNull = null; + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); - #endregion + Action config = () => outerNull.WrapAsync(retry); - #region Static configuration syntax tests, strongly-typed policies + config.Should().Throw().And.ParamName.Should().Be("outerPolicy"); + } - [Fact] - public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() - { - Action config = () => Policy.WrapAsync(); + [Fact] + public void Generic_interface_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + Action config = () => retry.WrapAsync((AsyncPolicy)null); - [Fact] - public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() - { - AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); - Action config = () => Policy.WrapAsync(new[] { singlePolicy }); + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - config.Should().Throw().And.ParamName.Should().Be("policies"); - } + [Fact] + public void Generic_interface_wraps_generic_instance_syntax_wrapping_null_should_throw() + { + IAsyncPolicy retry = Policy.HandleResult(0).RetryAsync(1); - [Fact] - public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - Action config = () => Policy.WrapAsync(new[] { retry, breaker }); + Action config = () => retry.WrapAsync((AsyncPolicy)null); - config.Should().NotThrow(); - } + config.Should().Throw().And.ParamName.Should().Be("innerPolicy"); + } - [Fact] - public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() - { - AsyncPolicy retry = Policy.Handle().RetryAsync(); - AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); - AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + [Fact] + public void Generic_interface_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); - Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); + IPolicyWrap wrap = policyA.WrapAsync(policyB); - config.Should().NotThrow(); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() - { - AsyncPolicy policyA = Policy.NoOpAsync(); - AsyncPolicy policyB = Policy.NoOpAsync(); + [Fact] + public void Generic_interface_wraps_generic_using_instance_wrap_syntax_should_set_outer_inner() + { + IAsyncPolicy policyA = Policy.NoOpAsync(); + IAsyncPolicy policyB = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(policyA, policyB); + IPolicyWrap wrap = policyA.WrapAsync(policyB); - wrap.Outer.Should().BeSameAs(policyA); - wrap.Inner.Should().BeSameAs(policyB); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - #endregion + #endregion - #region Instance-configured: execution tests + #region Static configuration syntax tests, non-generic policies - [Fact] - public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = retry.WrapAsync(breaker); - var breakerWrappingRetry = breaker.WrapAsync(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Wrapping_nothing_using_static_wrap_syntax_should_throw() + { + Action config = () => Policy.WrapAsync(); - [Fact] - public async Task Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = retry.WrapAsync(breaker); - var breakerWrappingRetry = breaker.WrapAsync(retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - #endregion + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_syntax_should_throw() + { + AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); + Action config = () => Policy.WrapAsync(new[] { singlePolicy }); - #region Static-configured: execution tests + config.Should().Throw().And.ParamName.Should().Be("policies"); + } - [Fact] - public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = Policy.WrapAsync(retry, breaker); - var breakerWrappingRetry = Policy.WrapAsync(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) - .Should().Throw(); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + [Fact] + public void Wrapping_two_policies_using_static_wrap_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.WrapAsync(new[] { retry, breaker }); - [Fact] - public async Task Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() - { - var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); - - var retryWrappingBreaker = Policy.WrapAsync(retry, breaker); - var breakerWrappingRetry = Policy.WrapAsync(breaker, retry); - - // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. - breaker.Reset(); - (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Open); - - // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. - breaker.Reset(); - (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) - .Should().Be(ResultPrimitive.Fault); - breaker.CircuitState.Should().Be(CircuitState.Closed); - } + config.Should().NotThrow(); + } - #endregion + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(1); + AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); - #region ExecuteAndCaptureAsyncSpecs + Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); - [Fact] - public async Task Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = - await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - } + config.Should().NotThrow(); + } - [Fact] - public async Task Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = - await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - } + [Fact] + public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); - [Fact] - public async Task Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); - } + var wrap = Policy.WrapAsync(policyA, policyB); - [Fact] - public async Task Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() - { - var innerHandlingDBZE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingANE = Policy - .Handle() - .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); - } + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } - [Fact] - public async Task Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() - { - var innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); - - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } + #endregion + + #region Static configuration syntax tests, strongly-typed policies - [Fact] - public async Task Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() - { - var innerHandlingFaultAgain = Policy - .HandleResult(ResultPrimitive.FaultAgain) - .CircuitBreakerAsync(1, TimeSpan.Zero); - var outerHandlingFault = Policy - .HandleResult(ResultPrimitive.Fault) - .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); - - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); - - executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); - executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); - executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); - executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); - } + [Fact] + public void Wrapping_nothing_using_static_wrap_strongly_typed_syntax_should_throw() + { + Action config = () => Policy.WrapAsync(); - #endregion + config.Should().Throw().And.ParamName.Should().Be("policies"); + } + + [Fact] + public void Wrapping_only_one_policy_using_static_wrap_strongly_typed_syntax_should_throw() + { + AsyncPolicy singlePolicy = Policy.Handle().RetryAsync(); + Action config = () => Policy.WrapAsync(new[] { singlePolicy }); -} \ No newline at end of file + config.Should().Throw().And.ParamName.Should().Be("policies"); + } + + [Fact] + public void Wrapping_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + Action config = () => Policy.WrapAsync(new[] { retry, breaker }); + + config.Should().NotThrow(); + } + + [Fact] + public void Wrapping_more_than_two_policies_using_static_wrap_strongly_typed_syntax_should_not_throw() + { + AsyncPolicy retry = Policy.Handle().RetryAsync(); + AsyncPolicy divideByZeroRetry = Policy.Handle().RetryAsync(2); + AsyncPolicy breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromSeconds(10)); + + Action config = () => Policy.WrapAsync(new[] { divideByZeroRetry, retry, breaker }); + + config.Should().NotThrow(); + } + + [Fact] + public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set_outer_inner() + { + AsyncPolicy policyA = Policy.NoOpAsync(); + AsyncPolicy policyB = Policy.NoOpAsync(); + + var wrap = Policy.WrapAsync(policyA, policyB); + + wrap.Outer.Should().BeSameAs(policyA); + wrap.Inner.Should().BeSameAs(policyB); + } + + #endregion + + #region Instance-configured: execution tests + + [Fact] + public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = retry.WrapAsync(breaker); + var breakerWrappingRetry = breaker.WrapAsync(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public async Task Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = retry.WrapAsync(breaker); + var breakerWrappingRetry = breaker.WrapAsync(retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #region Static-configured: execution tests + + [Fact] + public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = Policy.WrapAsync(retry, breaker); + var breakerWrappingRetry = Policy.WrapAsync(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + retryWrappingBreaker.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + breakerWrappingRetry.Awaiting(x => x.RaiseExceptionAsync(2)) + .Should().Throw(); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + [Fact] + public async Task Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() + { + var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1); // Two tries in total: first try, plus one retry. + var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreakerAsync(2, TimeSpan.MaxValue); + + var retryWrappingBreaker = Policy.WrapAsync(retry, breaker); + var breakerWrappingRetry = Policy.WrapAsync(breaker, retry); + + // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. + breaker.Reset(); + (await retryWrappingBreaker.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Open); + + // When the breaker wraps the retry, the retry (being inner) should retry twice before throwing the exception back on the breaker - the exception only hits the breaker once - so the breaker should not break. + breaker.Reset(); + (await breakerWrappingRetry.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault)) + .Should().Be(ResultPrimitive.Fault); + breaker.CircuitState.Should().Be(CircuitState.Closed); + } + + #endregion + + #region ExecuteAndCaptureAsyncSpecs + + [Fact] + public async Task Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = + await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + } + + [Fact] + public async Task Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = + await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + } + + [Fact] + public async Task Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ExceptionHandledByThisPolicy); + } + + [Fact] + public async Task Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() + { + var innerHandlingDBZE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingANE = Policy + .Handle() + .CircuitBreakerAsync(1, TimeSpan.Zero); + var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + + var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().Be(ExceptionType.Unhandled); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.UnhandledException); + } + + [Fact] + public async Task Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() + { + var innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.Zero); + var wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); + + var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(ResultPrimitive.Fault); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); + } + + [Fact] + public async Task Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() + { + var innerHandlingFaultAgain = Policy + .HandleResult(ResultPrimitive.FaultAgain) + .CircuitBreakerAsync(1, TimeSpan.Zero); + var outerHandlingFault = Policy + .HandleResult(ResultPrimitive.Fault) + .CircuitBreakerAsync(1, TimeSpan.Zero); + var wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); + + var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); + + executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); + executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); + executeAndCaptureResultOnPolicyWrap.FaultType.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeNull(); + executeAndCaptureResultOnPolicyWrap.ExceptionType.Should().BeNull(); + } + + #endregion + + } +} diff --git a/src/Polly/AsyncPolicy.ContextAndKeys.cs b/src/Polly/AsyncPolicy.ContextAndKeys.cs index 4660c69e059..97d0c87aa03 100644 --- a/src/Polly/AsyncPolicy.ContextAndKeys.cs +++ b/src/Polly/AsyncPolicy.ContextAndKeys.cs @@ -1,60 +1,61 @@ -namespace Polly; - -public abstract partial class AsyncPolicy +namespace Polly { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public AsyncPolicy WithPolicyKey(string policyKey) + public abstract partial class AsyncPolicy { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public AsyncPolicy WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } + + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } - policyKeyInternal = policyKey; - return this; } - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) + public abstract partial class AsyncPolicy { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public AsyncPolicy WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } + + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } } - } - -public abstract partial class AsyncPolicy -{ - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public AsyncPolicy WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } - - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy IAsyncPolicy.WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } -} \ No newline at end of file diff --git a/src/Polly/AsyncPolicy.ExecuteOverloads.cs b/src/Polly/AsyncPolicy.ExecuteOverloads.cs index 0bed81a7826..ef70f37f199 100644 --- a/src/Polly/AsyncPolicy.ExecuteOverloads.cs +++ b/src/Polly/AsyncPolicy.ExecuteOverloads.cs @@ -4,476 +4,477 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly; - -public abstract partial class AsyncPolicy : PolicyBase, IAsyncPolicy +namespace Polly { - #region ExecuteAsync overloads - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - [DebuggerStepThrough] - public Task ExecuteAsync(Func action) - => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - [DebuggerStepThrough] - public Task ExecuteAsync(Func action, IDictionary contextData) - => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - [DebuggerStepThrough] - public Task ExecuteAsync(Func action, Context context) - => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// 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); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// 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, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// 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, Context context, CancellationToken cancellationToken) - => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// 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); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - [DebuggerStepThrough] - public Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + public abstract partial class AsyncPolicy : PolicyBase, IAsyncPolicy { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); - - try + #region ExecuteAsync overloads + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + [DebuggerStepThrough] + public Task ExecuteAsync(Func action) + => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + [DebuggerStepThrough] + public Task ExecuteAsync(Func action, IDictionary contextData) + => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + [DebuggerStepThrough] + public Task ExecuteAsync(Func action, Context context) + => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// 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); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// 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, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// 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, Context context, CancellationToken cancellationToken) + => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// 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); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + [DebuggerStepThrough] + public Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { - await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + + try + { + await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } - } - #region Overloads method-generic in TResult - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action) - => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData) - => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context) - => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); - - try + #region Overloads method-generic in TResult + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action) + => ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData) + => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context) + => ExecuteAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + + try + { + return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } - } - #endregion - - #endregion - - #region ExecuteAndCaptureAsync overloads - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action) - => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, Context context) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// 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, Context context, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// Please 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return PolicyResult.Successful(context); - } - catch (Exception exception) + #endregion + + #endregion + + #region ExecuteAndCaptureAsync overloads + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action) + => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, Context context) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// 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, Context context, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// Please 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + if (context == null) throw new ArgumentNullException(nameof(context)); + + try + { + await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return PolicyResult.Successful(context); + } + catch (Exception exception) + { + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + } } - } - #region Overloads method-generic in TResult - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action) - => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - return PolicyResult.Successful( - await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext), context); - } - catch (Exception exception) + #region Overloads method-generic in TResult + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action) + => ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, DefaultCancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + if (context == null) throw new ArgumentNullException(nameof(context)); + + try + { + return PolicyResult.Successful( + await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext), context); + } + catch (Exception exception) + { + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + } } - } - #endregion + #endregion - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly/AsyncPolicy.GenericImplementation.cs b/src/Polly/AsyncPolicy.GenericImplementation.cs index c06d1d51d8c..85b0d27d92b 100644 --- a/src/Polly/AsyncPolicy.GenericImplementation.cs +++ b/src/Polly/AsyncPolicy.GenericImplementation.cs @@ -2,22 +2,23 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly; - -public abstract partial class AsyncPolicy +namespace Polly { - /// - /// Defines the implementation of a policy for async executions returning . - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// Whether async continuations should continue on a captured context. - /// A representing the result of the execution. - protected abstract Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext - ); -} \ No newline at end of file + public abstract partial class AsyncPolicy + { + /// + /// Defines the implementation of a policy for async executions returning . + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// Whether async continuations should continue on a captured context. + /// A representing the result of the execution. + protected abstract Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext + ); + } +} diff --git a/src/Polly/AsyncPolicy.NonGenericImplementation.cs b/src/Polly/AsyncPolicy.NonGenericImplementation.cs index 0ab138ffa28..8c856542682 100644 --- a/src/Polly/AsyncPolicy.NonGenericImplementation.cs +++ b/src/Polly/AsyncPolicy.NonGenericImplementation.cs @@ -3,43 +3,44 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly; - -public abstract partial class AsyncPolicy +namespace Polly { - /// - /// Defines the implementation of a policy for async executions with no return value. - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// Whether async continuations should continue on a captured context. - /// A representing the result of the execution. - protected virtual Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - => ImplementationAsync(async (ctx, token) => - { - await action(ctx, token).ConfigureAwait(continueOnCapturedContext); - return EmptyStruct.Instance; - }, context, cancellationToken, continueOnCapturedContext); + public abstract partial class AsyncPolicy + { + /// + /// Defines the implementation of a policy for async executions with no return value. + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// Whether async continuations should continue on a captured context. + /// A representing the result of the execution. + protected virtual Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + => ImplementationAsync(async (ctx, token) => + { + await action(ctx, token).ConfigureAwait(continueOnCapturedContext); + return EmptyStruct.Instance; + }, context, cancellationToken, continueOnCapturedContext); - /// - /// Defines the implementation of a policy for async executions returning . - /// - /// The type returned by asynchronous executions through the implementation. - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// Whether async continuations should continue on a captured context. - /// A representing the result of the execution. - protected abstract Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext - ); + /// + /// Defines the implementation of a policy for async executions returning . + /// + /// The type returned by asynchronous executions through the implementation. + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// Whether async continuations should continue on a captured context. + /// A representing the result of the execution. + protected abstract Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext + ); -} \ No newline at end of file + } +} diff --git a/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs b/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs index 4af101b5e00..be62b0a07d4 100644 --- a/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs +++ b/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs @@ -4,248 +4,249 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly; - -public abstract partial class AsyncPolicy : IAsyncPolicy +namespace Polly { - - #region ExecuteAsync overloads - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action) - => ExecuteAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData) - => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context) - => ExecuteAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + public abstract partial class AsyncPolicy : IAsyncPolicy { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); - try + #region ExecuteAsync overloads + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action) + => ExecuteAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData) + => ExecuteAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context) + => ExecuteAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + + try + { + return await ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } - finally + + #endregion + + #region ExecuteAndCaptureAsync overloads + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action) + => ExecuteAndCaptureAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context) + => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// Please 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); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) + => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + [DebuggerStepThrough] + public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) + => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + [DebuggerStepThrough] + public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } - } + if (context == null) throw new ArgumentNullException(nameof(context)); - #endregion - - #region ExecuteAndCaptureAsync overloads - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action) - => ExecuteAndCaptureAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), new Context(contextData), CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context) - => ExecuteAndCaptureAsync((ctx, _) => action(ctx), context, CancellationToken.None, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// Please 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); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken) - => ExecuteAndCaptureAsync(action, context, cancellationToken, DefaultContinueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - [DebuggerStepThrough] - public Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext) - => ExecuteAndCaptureAsync(action, new Context(contextData), cancellationToken, continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - [DebuggerStepThrough] - public async Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - if (context == null) throw new ArgumentNullException(nameof(context)); + try + { + var result = await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - try - { - var result = await ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + if (ResultPredicates.AnyMatch(result)) + { + return PolicyResult.Failure(result, context); + } - if (ResultPredicates.AnyMatch(result)) + return PolicyResult.Successful(result, context); + } + catch (Exception exception) { - return PolicyResult.Failure(result, context); + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } - - return PolicyResult.Successful(result, context); - } - catch (Exception exception) - { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } - } - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly/AsyncPolicy.TResult.cs b/src/Polly/AsyncPolicy.TResult.cs index b23ee6e4987..50a83d7d84b 100644 --- a/src/Polly/AsyncPolicy.TResult.cs +++ b/src/Polly/AsyncPolicy.TResult.cs @@ -1,29 +1,30 @@ -namespace Polly; - -/// -/// Transient exception handling policies that can be applied to asynchronous delegates -/// -/// The return type of delegates which may be executed through the policy. -public abstract partial class AsyncPolicy : PolicyBase +namespace Polly { /// - /// Constructs a new instance of a derived type with the passed and . + /// Transient exception handling policies that can be applied to asynchronous delegates /// - /// Predicates indicating which exceptions the policy should handle. - /// Predicates indicating which results the policy should handle. - internal AsyncPolicy( - ExceptionPredicates exceptionPredicates, - ResultPredicates resultPredicates) - : base(exceptionPredicates, resultPredicates) + /// The return type of delegates which may be executed through the policy. + public abstract partial class AsyncPolicy : PolicyBase { - } + /// + /// Constructs a new instance of a derived type with the passed and . + /// + /// Predicates indicating which exceptions the policy should handle. + /// Predicates indicating which results the policy should handle. + internal AsyncPolicy( + ExceptionPredicates exceptionPredicates, + ResultPredicates resultPredicates) + : base(exceptionPredicates, resultPredicates) + { + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A indicating which exceptions and results the policy should handle. - protected AsyncPolicy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A indicating which exceptions and results the policy should handle. + protected AsyncPolicy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { + } } -} \ No newline at end of file +} diff --git a/src/Polly/AsyncPolicy.cs b/src/Polly/AsyncPolicy.cs index 9232f3d632b..e4ab8f73e3c 100644 --- a/src/Polly/AsyncPolicy.cs +++ b/src/Polly/AsyncPolicy.cs @@ -1,25 +1,26 @@ -namespace Polly; - -/// -/// Transient exception handling policies that can be applied to asynchronous delegates -/// -public abstract partial class AsyncPolicy +namespace Polly { /// - /// Constructs a new instance of a derived type with the passed . + /// Transient exception handling policies that can be applied to asynchronous delegates /// - /// Predicates indicating which exceptions the policy should handle. - internal AsyncPolicy(ExceptionPredicates exceptionPredicates) - : base(exceptionPredicates) + public abstract partial class AsyncPolicy { - } + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// Predicates indicating which exceptions the policy should handle. + internal AsyncPolicy(ExceptionPredicates exceptionPredicates) + : base(exceptionPredicates) + { + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A specifying which exceptions the policy should handle. - protected AsyncPolicy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A specifying which exceptions the policy should handle. + protected AsyncPolicy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { + } } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/AsyncBulkheadEngine.cs b/src/Polly/Bulkhead/AsyncBulkheadEngine.cs index a5ba5933861..dd6cdf12f1b 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadEngine.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadEngine.cs @@ -2,40 +2,41 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Bulkhead; - -internal static class AsyncBulkheadEngine +namespace Polly.Bulkhead { - internal static async Task ImplementationAsync( - Func> action, - Context context, - Func onBulkheadRejectedAsync, - SemaphoreSlim maxParallelizationSemaphore, - SemaphoreSlim maxQueuedActionsSemaphore, - CancellationToken cancellationToken, - bool continueOnCapturedContext) + internal static class AsyncBulkheadEngine { - if (!await maxQueuedActionsSemaphore.WaitAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(continueOnCapturedContext)) - { - await onBulkheadRejectedAsync(context).ConfigureAwait(continueOnCapturedContext); - throw new BulkheadRejectedException(); - } - try + internal static async Task ImplementationAsync( + Func> action, + Context context, + Func onBulkheadRejectedAsync, + SemaphoreSlim maxParallelizationSemaphore, + SemaphoreSlim maxQueuedActionsSemaphore, + CancellationToken cancellationToken, + bool continueOnCapturedContext) { - await maxParallelizationSemaphore.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext); - - try + if (!await maxQueuedActionsSemaphore.WaitAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(continueOnCapturedContext)) { - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + await onBulkheadRejectedAsync(context).ConfigureAwait(continueOnCapturedContext); + throw new BulkheadRejectedException(); + } + try + { + await maxParallelizationSemaphore.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext); + + try + { + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } + finally + { + maxParallelizationSemaphore.Release(); + } } finally { - maxParallelizationSemaphore.Release(); + maxQueuedActionsSemaphore.Release(); } } - finally - { - maxQueuedActionsSemaphore.Release(); - } } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs b/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs index c7c5c00ce09..d5b445024ee 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadPolicy.cs @@ -3,98 +3,99 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Bulkhead; - -/// -/// A bulkhead-isolation policy which can be applied to delegates. -/// -public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy +namespace Polly.Bulkhead { - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private Func _onBulkheadRejectedAsync; - - internal AsyncBulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Func onBulkheadRejectedAsync) - { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. + /// A bulkhead-isolation policy which can be applied to delegates. /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; - - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); - - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) + public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy { - return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); - } + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private Func _onBulkheadRejectedAsync; - /// - public void Dispose() - { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); - } -} + internal AsyncBulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Func onBulkheadRejectedAsync) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); -/// -/// A bulkhead-isolation policy which can be applied to delegates. -/// -/// The return type of delegates which may be executed through the policy. -public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy -{ - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private Func _onBulkheadRejectedAsync; + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } - internal AsyncBulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Func onBulkheadRejectedAsync) - { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + } + + /// + public void Dispose() + { + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); + } } /// - /// Gets the number of slots currently available for executing actions through the bulkhead. + /// A bulkhead-isolation policy which can be applied to delegates. /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + /// The return type of delegates which may be executed through the policy. + public class AsyncBulkheadPolicy : AsyncPolicy, IBulkheadPolicy + { + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private Func _onBulkheadRejectedAsync; - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + internal AsyncBulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Func onBulkheadRejectedAsync) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejectedAsync = onBulkheadRejectedAsync ?? throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - /// - public void Dispose() - { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + return AsyncBulkheadEngine.ImplementationAsync(action, context, _onBulkheadRejectedAsync, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken, continueOnCapturedContext); + } + + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + + /// + public void Dispose() + { + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); + } } } \ No newline at end of file diff --git a/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs b/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs index b4b81e7061f..d51fad7644c 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadSyntax.cs @@ -3,74 +3,75 @@ using System; using System.Threading.Tasks; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) + public partial class Policy { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, 0, doNothingAsync); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) + { + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, 0, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejectedAsync - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) - => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejectedAsync + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) + => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) - { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) + { + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejectedAsync - public static AsyncBulkheadPolicy BulkheadAsync( - int maxParallelization, - int maxQueuingActions, - Func onBulkheadRejectedAsync) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejectedAsync + public static AsyncBulkheadPolicy BulkheadAsync( + int maxParallelization, + int maxQueuingActions, + Func onBulkheadRejectedAsync) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - return new AsyncBulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejectedAsync - ); + return new AsyncBulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejectedAsync + ); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs b/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs index e42c4ad4f49..8f43e849fd5 100644 --- a/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs +++ b/src/Polly/Bulkhead/AsyncBulkheadTResultSyntax.cs @@ -3,71 +3,72 @@ using System; using System.Threading.Tasks; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) + public partial class Policy { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, 0, doNothingAsync); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization) + { + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, 0, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejectedAsync - /// The policy instance. - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) - => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejectedAsync + /// The policy instance. + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, Func onBulkheadRejectedAsync) + => BulkheadAsync(maxParallelization, 0, onBulkheadRejectedAsync); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejectedAsync - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) - { - Func doNothingAsync = _ => TaskHelper.EmptyTask; - return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejectedAsync + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions) + { + Func doNothingAsync = _ => TaskHelper.EmptyTask; + return BulkheadAsync(maxParallelization, maxQueuingActions, doNothingAsync); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejectedAsync - public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions, Func onBulkheadRejectedAsync) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call asynchronously, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejectedAsync + public static AsyncBulkheadPolicy BulkheadAsync(int maxParallelization, int maxQueuingActions, Func onBulkheadRejectedAsync) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejectedAsync == null) throw new ArgumentNullException(nameof(onBulkheadRejectedAsync)); - return new AsyncBulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejectedAsync - ); + return new AsyncBulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejectedAsync + ); + } } } \ No newline at end of file diff --git a/src/Polly/Bulkhead/BulkheadEngine.cs b/src/Polly/Bulkhead/BulkheadEngine.cs index d3531621842..2d022848b46 100644 --- a/src/Polly/Bulkhead/BulkheadEngine.cs +++ b/src/Polly/Bulkhead/BulkheadEngine.cs @@ -1,39 +1,40 @@ using System; using System.Threading; -namespace Polly.Bulkhead; - -internal static class BulkheadEngine +namespace Polly.Bulkhead { - internal static TResult Implementation( - Func action, - Context context, - Action onBulkheadRejected, - SemaphoreSlim maxParallelizationSemaphore, - SemaphoreSlim maxQueuedActionsSemaphore, - CancellationToken cancellationToken) + internal static class BulkheadEngine { - if (!maxQueuedActionsSemaphore.Wait(TimeSpan.Zero, cancellationToken)) + internal static TResult Implementation( + Func action, + Context context, + Action onBulkheadRejected, + SemaphoreSlim maxParallelizationSemaphore, + SemaphoreSlim maxQueuedActionsSemaphore, + CancellationToken cancellationToken) { - onBulkheadRejected(context); - throw new BulkheadRejectedException(); - } + if (!maxQueuedActionsSemaphore.Wait(TimeSpan.Zero, cancellationToken)) + { + onBulkheadRejected(context); + throw new BulkheadRejectedException(); + } - try - { - maxParallelizationSemaphore.Wait(cancellationToken); try { - return action(context, cancellationToken); + maxParallelizationSemaphore.Wait(cancellationToken); + try + { + return action(context, cancellationToken); + } + finally + { + maxParallelizationSemaphore.Release(); + } } finally { - maxParallelizationSemaphore.Release(); + maxQueuedActionsSemaphore.Release(); } } - finally - { - maxQueuedActionsSemaphore.Release(); - } } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/BulkheadPolicy.cs b/src/Polly/Bulkhead/BulkheadPolicy.cs index 9ec5cefea3b..dd8125cabd7 100644 --- a/src/Polly/Bulkhead/BulkheadPolicy.cs +++ b/src/Polly/Bulkhead/BulkheadPolicy.cs @@ -2,99 +2,100 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Bulkhead; - -/// -/// A bulkhead-isolation policy which can be applied to delegates. -/// -public class BulkheadPolicy : Policy, IBulkheadPolicy +namespace Polly.Bulkhead { - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private readonly Action _onBulkheadRejected; - - internal BulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Action onBulkheadRejected) + /// + /// A bulkhead-isolation policy which can be applied to delegates. + /// + public class BulkheadPolicy : Policy, IBulkheadPolicy { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private readonly Action _onBulkheadRejected; - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } + internal BulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Action onBulkheadRejected) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. - /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); + + /// + /// Disposes of the , allowing it to dispose its internal resources. + /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. + /// + public void Dispose() + { + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); + } + } /// - /// Disposes of the , allowing it to dispose its internal resources. - /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. + /// A bulkhead-isolation policy which can be applied to delegates returning a value of type . /// - public void Dispose() + public class BulkheadPolicy : Policy, IBulkheadPolicy { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); - } -} + private readonly SemaphoreSlim _maxParallelizationSemaphore; + private readonly SemaphoreSlim _maxQueuedActionsSemaphore; + private readonly int _maxQueueingActions; + private readonly Action _onBulkheadRejected; -/// -/// A bulkhead-isolation policy which can be applied to delegates returning a value of type . -/// -public class BulkheadPolicy : Policy, IBulkheadPolicy -{ - private readonly SemaphoreSlim _maxParallelizationSemaphore; - private readonly SemaphoreSlim _maxQueuedActionsSemaphore; - private readonly int _maxQueueingActions; - private readonly Action _onBulkheadRejected; + /// + internal BulkheadPolicy( + int maxParallelization, + int maxQueueingActions, + Action onBulkheadRejected) + { + _maxQueueingActions = maxQueueingActions; + _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); - /// - internal BulkheadPolicy( - int maxParallelization, - int maxQueueingActions, - Action onBulkheadRejected) - { - _maxQueueingActions = maxQueueingActions; - _onBulkheadRejected = onBulkheadRejected ?? throw new ArgumentNullException(nameof(onBulkheadRejected)); + (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); + } - (_maxParallelizationSemaphore, _maxQueuedActionsSemaphore) = BulkheadSemaphoreFactory.CreateBulkheadSemaphores(maxParallelization, maxQueueingActions); - } + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => BulkheadEngine.Implementation(action, context, _onBulkheadRejected, _maxParallelizationSemaphore, _maxQueuedActionsSemaphore, cancellationToken); + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; - /// - /// Gets the number of slots currently available for executing actions through the bulkhead. - /// - public int BulkheadAvailableCount => _maxParallelizationSemaphore.CurrentCount; + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); - /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. - /// - public int QueueAvailableCount => Math.Min(_maxQueuedActionsSemaphore.CurrentCount, _maxQueueingActions); - - /// - /// Disposes of the , allowing it to dispose its internal resources. - /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. - /// - public void Dispose() - { - _maxParallelizationSemaphore.Dispose(); - _maxQueuedActionsSemaphore.Dispose(); + /// + /// Disposes of the , allowing it to dispose its internal resources. + /// Only call on a after all actions executed through the policy have completed. If actions are still executing through the policy when is called, an may be thrown on the actions' threads when those actions complete. + /// + public void Dispose() + { + _maxParallelizationSemaphore.Dispose(); + _maxQueuedActionsSemaphore.Dispose(); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs b/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs index f1dc0671281..d9c3cf57815 100644 --- a/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs +++ b/src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs @@ -1,18 +1,19 @@ using System.Threading; -namespace Polly.Bulkhead; - -internal static class BulkheadSemaphoreFactory +namespace Polly.Bulkhead { - public static (SemaphoreSlim, SemaphoreSlim) CreateBulkheadSemaphores(int maxParallelization, int maxQueueingActions) + internal static class BulkheadSemaphoreFactory { - var maxParallelizationSemaphore = new SemaphoreSlim(maxParallelization, maxParallelization); + public static (SemaphoreSlim, SemaphoreSlim) CreateBulkheadSemaphores(int maxParallelization, int maxQueueingActions) + { + var maxParallelizationSemaphore = new SemaphoreSlim(maxParallelization, maxParallelization); - var maxQueuingCompounded = maxQueueingActions <= int.MaxValue - maxParallelization - ? maxQueueingActions + maxParallelization - : int.MaxValue; - var maxQueuedActionsSemaphore = new SemaphoreSlim(maxQueuingCompounded, maxQueuingCompounded); + var maxQueuingCompounded = maxQueueingActions <= int.MaxValue - maxParallelization + ? maxQueueingActions + maxParallelization + : int.MaxValue; + var maxQueuedActionsSemaphore = new SemaphoreSlim(maxQueuingCompounded, maxQueuingCompounded); - return (maxParallelizationSemaphore, maxQueuedActionsSemaphore); + return (maxParallelizationSemaphore, maxQueuedActionsSemaphore); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Bulkhead/BulkheadSyntax.cs b/src/Polly/Bulkhead/BulkheadSyntax.cs index a84fdb686c6..8c3a0caa3a8 100644 --- a/src/Polly/Bulkhead/BulkheadSyntax.cs +++ b/src/Polly/Bulkhead/BulkheadSyntax.cs @@ -1,72 +1,73 @@ using Polly.Bulkhead; using System; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// maxParallelization;Value must be greater than zero. - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization) + public partial class Policy { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, 0, doNothing); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// maxParallelization;Value must be greater than zero. + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization) + { + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, 0, doNothing); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejected - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) - => Bulkhead(maxParallelization, 0, onBulkheadRejected); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejected + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) + => Bulkhead(maxParallelization, 0, onBulkheadRejected); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) - { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, maxQueuingActions, doNothing); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) + { + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, maxQueuingActions, doNothing); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejected - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejected + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); - return new BulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejected - ); - } + return new BulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejected + ); + } -} \ No newline at end of file + } +} diff --git a/src/Polly/Bulkhead/BulkheadTResultSyntax.cs b/src/Polly/Bulkhead/BulkheadTResultSyntax.cs index b6e7e67e7d0..df7d595e655 100644 --- a/src/Polly/Bulkhead/BulkheadTResultSyntax.cs +++ b/src/Polly/Bulkhead/BulkheadTResultSyntax.cs @@ -1,72 +1,73 @@ using Polly.Bulkhead; using System; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// maxParallelization;Value must be greater than zero. - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization) + public partial class Policy { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, 0, doNothing); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// maxParallelization;Value must be greater than zero. + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization) + { + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, 0, doNothing); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// maxParallelization;Value must be greater than zero. - /// onBulkheadRejected - /// The policy instance. - public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) - => Bulkhead(maxParallelization, 0, onBulkheadRejected); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the action is not executed and a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// maxParallelization;Value must be greater than zero. + /// onBulkheadRejected + /// The policy instance. + public static BulkheadPolicy Bulkhead(int maxParallelization, Action onBulkheadRejected) + => Bulkhead(maxParallelization, 0, onBulkheadRejected); - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) - { - Action doNothing = _ => { }; - return Bulkhead(maxParallelization, maxQueuingActions, doNothing); - } + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions) + { + Action doNothing = _ => { }; + return Bulkhead(maxParallelization, maxQueuingActions, doNothing); + } - /// - /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. - /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. - /// - /// The maximum number of concurrent actions that may be executing through the policy. - /// The maximum number of actions that may be queuing, waiting for an execution slot. - /// An action to call, if the bulkhead rejects execution due to oversubscription. - /// The policy instance. - /// maxParallelization;Value must be greater than zero. - /// maxQueuingActions;Value must be greater than or equal to zero. - /// onBulkheadRejected - public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) - { - if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); - if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); - if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); + /// + /// Builds a bulkhead isolation , which limits the maximum concurrency of actions executed through the policy. Imposing a maximum concurrency limits the potential of governed actions, when faulting, to bring down the system. + /// When an execution would cause the number of actions executing concurrently through the policy to exceed , the policy allows a further executions to queue, waiting for a concurrent execution slot. When an execution would cause the number of queuing actions to exceed , a is thrown. + /// + /// The maximum number of concurrent actions that may be executing through the policy. + /// The maximum number of actions that may be queuing, waiting for an execution slot. + /// An action to call, if the bulkhead rejects execution due to oversubscription. + /// The policy instance. + /// maxParallelization;Value must be greater than zero. + /// maxQueuingActions;Value must be greater than or equal to zero. + /// onBulkheadRejected + public static BulkheadPolicy Bulkhead(int maxParallelization, int maxQueuingActions, Action onBulkheadRejected) + { + if (maxParallelization <= 0) throw new ArgumentOutOfRangeException(nameof(maxParallelization), "Value must be greater than zero."); + if (maxQueuingActions < 0) throw new ArgumentOutOfRangeException(nameof(maxQueuingActions), "Value must be greater than or equal to zero."); + if (onBulkheadRejected == null) throw new ArgumentNullException(nameof(onBulkheadRejected)); - return new BulkheadPolicy( - maxParallelization, - maxQueuingActions, - onBulkheadRejected - ); - } + return new BulkheadPolicy( + maxParallelization, + maxQueuingActions, + onBulkheadRejected + ); + } -} \ No newline at end of file + } +} diff --git a/src/Polly/Bulkhead/IBulkheadPolicy.cs b/src/Polly/Bulkhead/IBulkheadPolicy.cs index df7113e54e9..64273314a2e 100644 --- a/src/Polly/Bulkhead/IBulkheadPolicy.cs +++ b/src/Polly/Bulkhead/IBulkheadPolicy.cs @@ -1,27 +1,29 @@ using System; -namespace Polly.Bulkhead; - -/// -/// Defines properties and methods common to all bulkhead policies. -/// -public interface IBulkheadPolicy : IsPolicy, IDisposable +namespace Polly.Bulkhead { /// - /// Gets the number of slots currently available for executing actions through the bulkhead. + /// Defines properties and methods common to all bulkhead policies. /// - int BulkheadAvailableCount { get; } + + public interface IBulkheadPolicy : IsPolicy, IDisposable + { + /// + /// Gets the number of slots currently available for executing actions through the bulkhead. + /// + int BulkheadAvailableCount { get; } + + /// + /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// + int QueueAvailableCount { get; } + } /// - /// Gets the number of slots currently available for queuing actions for execution through the bulkhead. + /// Defines properties and methods common to all bulkhead policies generic-typed for executions returning results of type . /// - int QueueAvailableCount { get; } -} - -/// -/// Defines properties and methods common to all bulkhead policies generic-typed for executions returning results of type . -/// -public interface IBulkheadPolicy : IBulkheadPolicy -{ + public interface IBulkheadPolicy : IBulkheadPolicy + { -} \ No newline at end of file + } +} diff --git a/src/Polly/Caching/AbsoluteTtl.cs b/src/Polly/Caching/AbsoluteTtl.cs index 5e5308c133e..06e8c831495 100644 --- a/src/Polly/Caching/AbsoluteTtl.cs +++ b/src/Polly/Caching/AbsoluteTtl.cs @@ -1,15 +1,16 @@ using System; -namespace Polly.Caching; - -/// -/// Defines a ttl strategy which will cache items until the specified point-in-time. -/// -public class AbsoluteTtl : NonSlidingTtl +namespace Polly.Caching { /// - /// Initializes a new instance of the class. + /// Defines a ttl strategy which will cache items until the specified point-in-time. /// - /// The UTC point in time until which to consider the cache item valid. - public AbsoluteTtl(DateTimeOffset absoluteExpirationTime) : base(absoluteExpirationTime) { } -} \ No newline at end of file + public class AbsoluteTtl : NonSlidingTtl + { + /// + /// Initializes a new instance of the class. + /// + /// The UTC point in time until which to consider the cache item valid. + public AbsoluteTtl(DateTimeOffset absoluteExpirationTime) : base(absoluteExpirationTime) { } + } +} diff --git a/src/Polly/Caching/AsyncCacheEngine.cs b/src/Polly/Caching/AsyncCacheEngine.cs index 5bcfbf8252f..09e587c9499 100644 --- a/src/Polly/Caching/AsyncCacheEngine.cs +++ b/src/Polly/Caching/AsyncCacheEngine.cs @@ -2,70 +2,71 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching; - -internal static class AsyncCacheEngine +namespace Polly.Caching { - internal static async Task ImplementationAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) + internal static class AsyncCacheEngine { - cancellationToken.ThrowIfCancellationRequested(); - - var cacheKey = cacheKeyStrategy(context); - if (cacheKey == null) + internal static async Task ImplementationAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) { - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + cancellationToken.ThrowIfCancellationRequested(); - bool cacheHit; - TResult valueFromCache; - try - { - (cacheHit, valueFromCache) = await cacheProvider.TryGetAsync(cacheKey, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - } - catch (Exception ex) - { - cacheHit = false; - valueFromCache = default; - onCacheGetError(context, cacheKey, ex); - } - if (cacheHit) - { - onCacheGet(context, cacheKey); - return valueFromCache; - } - else - { - onCacheMiss(context, cacheKey); - } - - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + var cacheKey = cacheKeyStrategy(context); + if (cacheKey == null) + { + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } - var ttl = ttlStrategy.GetTtl(context, result); - if (ttl.Timespan > TimeSpan.Zero) - { + bool cacheHit; + TResult valueFromCache; try { - await cacheProvider.PutAsync(cacheKey, result, ttl, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - onCachePut(context, cacheKey); + (cacheHit, valueFromCache) = await cacheProvider.TryGetAsync(cacheKey, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); } catch (Exception ex) { - onCachePutError(context, cacheKey, ex); + cacheHit = false; + valueFromCache = default; + onCacheGetError(context, cacheKey, ex); + } + if (cacheHit) + { + onCacheGet(context, cacheKey); + return valueFromCache; + } + else + { + onCacheMiss(context, cacheKey); + } + + var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + + var ttl = ttlStrategy.GetTtl(context, result); + if (ttl.Timespan > TimeSpan.Zero) + { + try + { + await cacheProvider.PutAsync(cacheKey, result, ttl, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + onCachePut(context, cacheKey); + } + catch (Exception ex) + { + onCachePutError(context, cacheKey, ex); + } } - } - return result; + return result; + } } } \ No newline at end of file diff --git a/src/Polly/Caching/AsyncCachePolicy.cs b/src/Polly/Caching/AsyncCachePolicy.cs index dd3ae0fc751..903c2bfdacb 100644 --- a/src/Polly/Caching/AsyncCachePolicy.cs +++ b/src/Polly/Caching/AsyncCachePolicy.cs @@ -3,130 +3,132 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching; - -/// -/// A cache policy that can be applied to the results of delegate executions. -/// -public class AsyncCachePolicy : AsyncPolicy +namespace Polly.Caching { - private readonly IAsyncCacheProvider _asyncCacheProvider; - private readonly ITtlStrategy _ttlStrategy; - private readonly Func _cacheKeyStrategy; + /// + /// A cache policy that can be applied to the results of delegate executions. + /// + public class AsyncCachePolicy : AsyncPolicy + { + private readonly IAsyncCacheProvider _asyncCacheProvider; + private readonly ITtlStrategy _ttlStrategy; + private readonly Func _cacheKeyStrategy; - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - internal AsyncCachePolicy( - IAsyncCacheProvider asyncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _asyncCacheProvider = asyncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; + internal AsyncCachePolicy( + IAsyncCacheProvider asyncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _asyncCacheProvider = asyncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; - } + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; + } - /// - protected override Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - // Pass-through/NOOP policy action, for void-returning executions through the cache policy. - return action(context, cancellationToken); + /// + protected override Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + // Pass-through/NOOP policy action, for void-returning executions through the cache policy. + return action(context, cancellationToken); + } + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncCacheEngine.ImplementationAsync( + _asyncCacheProvider.AsyncFor(), + _ttlStrategy.For(), + _cacheKeyStrategy, + action, + context, + cancellationToken, + continueOnCapturedContext, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); + } } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) + /// + /// A cache policy that can be applied to the results of delegate executions. + /// + /// The return type of delegates which may be executed through the policy. + public class AsyncCachePolicy : AsyncPolicy { - return AsyncCacheEngine.ImplementationAsync( - _asyncCacheProvider.AsyncFor(), - _ttlStrategy.For(), - _cacheKeyStrategy, - action, - context, - cancellationToken, - continueOnCapturedContext, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); - } -} + private IAsyncCacheProvider _asyncCacheProvider; + private readonly ITtlStrategy _ttlStrategy; + private readonly Func _cacheKeyStrategy; -/// -/// A cache policy that can be applied to the results of delegate executions. -/// -/// The return type of delegates which may be executed through the policy. -public class AsyncCachePolicy : AsyncPolicy -{ - private IAsyncCacheProvider _asyncCacheProvider; - private readonly ITtlStrategy _ttlStrategy; - private readonly Func _cacheKeyStrategy; + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; + internal AsyncCachePolicy( + IAsyncCacheProvider asyncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _asyncCacheProvider = asyncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - internal AsyncCachePolicy( - IAsyncCacheProvider asyncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _asyncCacheProvider = asyncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; + } - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncCacheEngine.ImplementationAsync( + _asyncCacheProvider, + _ttlStrategy, + _cacheKeyStrategy, + action, + context, + cancellationToken, + continueOnCapturedContext, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); + } } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncCacheEngine.ImplementationAsync( - _asyncCacheProvider, - _ttlStrategy, - _cacheKeyStrategy, - action, - context, - cancellationToken, - continueOnCapturedContext, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); - } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/AsyncCacheSyntax.cs b/src/Polly/Caching/AsyncCacheSyntax.cs index e2214911157..5a8529d6695 100644 --- a/src/Polly/Caching/AsyncCacheSyntax.cs +++ b/src/Polly/Caching/AsyncCacheSyntax.cs @@ -1,327 +1,328 @@ using Polly.Caching; using System; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + public partial class Policy + { + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds an that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds an that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/AsyncCacheTResultSyntax.cs b/src/Polly/Caching/AsyncCacheTResultSyntax.cs index e7f98e9a0c0..a38f3616935 100644 --- a/src/Polly/Caching/AsyncCacheTResultSyntax.cs +++ b/src/Polly/Caching/AsyncCacheTResultSyntax.cs @@ -1,822 +1,823 @@ using Polly.Caching; using System; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) + public partial class Policy { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy.For(), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds an that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static AsyncCachePolicy CacheAsync( - IAsyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - - return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return CacheAsync(cacheProvider.AsyncFor(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy.For(), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => CacheAsync(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds an that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static AsyncCachePolicy CacheAsync( + IAsyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + + return new AsyncCachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/AsyncGenericCacheProvider.cs b/src/Polly/Caching/AsyncGenericCacheProvider.cs index 1de776ac8ea..5d4be99a188 100644 --- a/src/Polly/Caching/AsyncGenericCacheProvider.cs +++ b/src/Polly/Caching/AsyncGenericCacheProvider.cs @@ -2,25 +2,26 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching; - -/// -/// Provides a strongly-typed wrapper over a non-generic CacheProviderAsync. -/// -/// The type of the objects in the cache. -internal class AsyncGenericCacheProvider : IAsyncCacheProvider +namespace Polly.Caching { - private readonly IAsyncCacheProvider _wrappedCacheProvider; + /// + /// Provides a strongly-typed wrapper over a non-generic CacheProviderAsync. + /// + /// The type of the objects in the cache. + internal class AsyncGenericCacheProvider : IAsyncCacheProvider + { + private readonly IAsyncCacheProvider _wrappedCacheProvider; - internal AsyncGenericCacheProvider(IAsyncCacheProvider nonGenericCacheProvider) - => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); + internal AsyncGenericCacheProvider(IAsyncCacheProvider nonGenericCacheProvider) + => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); - async Task<(bool, TCacheFormat)> IAsyncCacheProvider.TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) - { - (var cacheHit, var result) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return (cacheHit, (TCacheFormat)(result ?? default(TCacheFormat))); - } + async Task<(bool, TCacheFormat)> IAsyncCacheProvider.TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + (var cacheHit, var result) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (cacheHit, (TCacheFormat)(result ?? default(TCacheFormat))); + } - Task IAsyncCacheProvider.PutAsync(string key, TCacheFormat value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) - => _wrappedCacheProvider.PutAsync(key, value, ttl, cancellationToken, continueOnCapturedContext); -} \ No newline at end of file + Task IAsyncCacheProvider.PutAsync(string key, TCacheFormat value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) + => _wrappedCacheProvider.PutAsync(key, value, ttl, cancellationToken, continueOnCapturedContext); + } +} diff --git a/src/Polly/Caching/AsyncSerializingCacheProvider.cs b/src/Polly/Caching/AsyncSerializingCacheProvider.cs index f5c8a1111ff..86f2d4ae6a8 100644 --- a/src/Polly/Caching/AsyncSerializingCacheProvider.cs +++ b/src/Polly/Caching/AsyncSerializingCacheProvider.cs @@ -2,125 +2,126 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching; - -/// -/// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with asynchronous . -/// -/// The type of serialized objects to be placed in the cache. -public class AsyncSerializingCacheProvider : IAsyncCacheProvider +namespace Polly.Caching { - private readonly IAsyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; - - /// - /// Initializes a new instance of the class. - /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) - { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - } - /// - /// Gets a value from the cache asynchronously. + /// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with asynchronous . /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (null if not found). - /// - public async Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + /// The type of serialized objects to be placed in the cache. + public class AsyncSerializingCacheProvider : IAsyncCacheProvider { - (var cacheHit, var objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); - } + private readonly IAsyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// A which completes when the value has been cached. - public async Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - await _wrappedCacheProvider.PutAsync( - key, - _serializer.Serialize(value), - ttl, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); - } -} + /// + /// Initializes a new instance of the class. + /// + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) + { + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } -/// -/// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with asynchronous . -/// -/// The return type of delegates which may be executed through the policy. -/// The type of serialized objects to be placed in the cache. -public class AsyncSerializingCacheProvider : IAsyncCacheProvider -{ - private readonly IAsyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; + /// + /// Gets a value from the cache asynchronously. + /// + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (null if not found). + /// + public async Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + (var cacheHit, var objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); + } - /// - /// Initializes a new instance of the class. - /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) - { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + /// + /// Puts the specified value in the cache asynchronously. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// A which completes when the value has been cached. + public async Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + await _wrappedCacheProvider.PutAsync( + key, + _serializer.Serialize(value), + ttl, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); + } } /// - /// Gets a value from the cache asynchronously. + /// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with asynchronous . /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). - /// - public async Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + /// The return type of delegates which may be executed through the policy. + /// The type of serialized objects to be placed in the cache. + public class AsyncSerializingCacheProvider : IAsyncCacheProvider { - (var cacheHit, var objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); - } + private readonly IAsyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. - /// A which completes when the value has been cached. - public async Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - await _wrappedCacheProvider.PutAsync( - key, - _serializer.Serialize(value), - ttl, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + /// + /// Initializes a new instance of the class. + /// + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) + { + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } + + /// + /// Gets a value from the cache asynchronously. + /// + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). + /// + public async Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + (var cacheHit, var objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); + } + + /// + /// Puts the specified value in the cache asynchronously. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. + /// A which completes when the value has been cached. + public async Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + await _wrappedCacheProvider.PutAsync( + key, + _serializer.Serialize(value), + ttl, + cancellationToken, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/CacheEngine.cs b/src/Polly/Caching/CacheEngine.cs index ca4f173d652..a1c66b18551 100644 --- a/src/Polly/Caching/CacheEngine.cs +++ b/src/Polly/Caching/CacheEngine.cs @@ -1,69 +1,70 @@ using System; using System.Threading; -namespace Polly.Caching; - -internal static class CacheEngine +namespace Polly.Caching { - internal static TResult Implementation( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Func action, - Context context, - CancellationToken cancellationToken, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) + internal static class CacheEngine { - cancellationToken.ThrowIfCancellationRequested(); - - var cacheKey = cacheKeyStrategy(context); - if (cacheKey == null) + internal static TResult Implementation( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Func action, + Context context, + CancellationToken cancellationToken, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) { - return action(context, cancellationToken); - } + cancellationToken.ThrowIfCancellationRequested(); - bool cacheHit; - TResult valueFromCache; - try - { - (cacheHit, valueFromCache) = cacheProvider.TryGet(cacheKey); - } - catch (Exception ex) - { - cacheHit = false; - valueFromCache = default; - onCacheGetError(context, cacheKey, ex); - } - if (cacheHit) - { - onCacheGet(context, cacheKey); - return valueFromCache; - } - else - { - onCacheMiss(context, cacheKey); - } - - var result = action(context, cancellationToken); + var cacheKey = cacheKeyStrategy(context); + if (cacheKey == null) + { + return action(context, cancellationToken); + } - var ttl = ttlStrategy.GetTtl(context, result); - if (ttl.Timespan > TimeSpan.Zero) - { + bool cacheHit; + TResult valueFromCache; try { - cacheProvider.Put(cacheKey, result, ttl); - onCachePut(context, cacheKey); + (cacheHit, valueFromCache) = cacheProvider.TryGet(cacheKey); } catch (Exception ex) { - onCachePutError(context, cacheKey, ex); + cacheHit = false; + valueFromCache = default; + onCacheGetError(context, cacheKey, ex); + } + if (cacheHit) + { + onCacheGet(context, cacheKey); + return valueFromCache; + } + else + { + onCacheMiss(context, cacheKey); + } + + var result = action(context, cancellationToken); + + var ttl = ttlStrategy.GetTtl(context, result); + if (ttl.Timespan > TimeSpan.Zero) + { + try + { + cacheProvider.Put(cacheKey, result, ttl); + onCachePut(context, cacheKey); + } + catch (Exception ex) + { + onCachePutError(context, cacheKey, ex); + } } - } - return result; + return result; + } } } \ No newline at end of file diff --git a/src/Polly/Caching/CachePolicy.cs b/src/Polly/Caching/CachePolicy.cs index f740ac16c46..1c63070bc23 100644 --- a/src/Polly/Caching/CachePolicy.cs +++ b/src/Polly/Caching/CachePolicy.cs @@ -2,117 +2,118 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Caching; - -/// -/// A cache policy that can be applied to the results of delegate executions. -/// -public class CachePolicy : Policy, ICachePolicy +namespace Polly.Caching { - private readonly ISyncCacheProvider _syncCacheProvider; - private readonly ITtlStrategy _ttlStrategy; - private readonly Func _cacheKeyStrategy; + /// + /// A cache policy that can be applied to the results of delegate executions. + /// + public class CachePolicy : Policy, ICachePolicy + { + private readonly ISyncCacheProvider _syncCacheProvider; + private readonly ITtlStrategy _ttlStrategy; + private readonly Func _cacheKeyStrategy; - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - internal CachePolicy( - ISyncCacheProvider syncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _syncCacheProvider = syncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; + internal CachePolicy( + ISyncCacheProvider syncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _syncCacheProvider = syncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; - } + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; + } - /// - protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) - // Pass-through/NOOP policy action, for void-returning calls through a cache policy. - => action(context, cancellationToken); + /// + protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) + // Pass-through/NOOP policy action, for void-returning calls through a cache policy. + => action(context, cancellationToken); - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - return CacheEngine.Implementation( - _syncCacheProvider.For(), - _ttlStrategy.For(), - _cacheKeyStrategy, - action, - context, - cancellationToken, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + return CacheEngine.Implementation( + _syncCacheProvider.For(), + _ttlStrategy.For(), + _cacheKeyStrategy, + action, + context, + cancellationToken, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); + } } -} -/// -/// A cache policy that can be applied to the results of delegate executions. -/// -public class CachePolicy : Policy, ICachePolicy -{ - private ISyncCacheProvider _syncCacheProvider; - private ITtlStrategy _ttlStrategy; - private Func _cacheKeyStrategy; + /// + /// A cache policy that can be applied to the results of delegate executions. + /// + public class CachePolicy : Policy, ICachePolicy + { + private ISyncCacheProvider _syncCacheProvider; + private ITtlStrategy _ttlStrategy; + private Func _cacheKeyStrategy; - private readonly Action _onCacheGet; - private readonly Action _onCacheMiss; - private readonly Action _onCachePut; - private readonly Action _onCacheGetError; - private readonly Action _onCachePutError; + private readonly Action _onCacheGet; + private readonly Action _onCacheMiss; + private readonly Action _onCachePut; + private readonly Action _onCacheGetError; + private readonly Action _onCachePutError; - internal CachePolicy( - ISyncCacheProvider syncCacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - _syncCacheProvider = syncCacheProvider; - _ttlStrategy = ttlStrategy; - _cacheKeyStrategy = cacheKeyStrategy; + internal CachePolicy( + ISyncCacheProvider syncCacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + _syncCacheProvider = syncCacheProvider; + _ttlStrategy = ttlStrategy; + _cacheKeyStrategy = cacheKeyStrategy; - _onCacheGet = onCacheGet; - _onCachePut = onCachePut; - _onCacheMiss = onCacheMiss; - _onCacheGetError = onCacheGetError; - _onCachePutError = onCachePutError; - } + _onCacheGet = onCacheGet; + _onCachePut = onCachePut; + _onCacheMiss = onCacheMiss; + _onCacheGetError = onCacheGetError; + _onCachePutError = onCachePutError; + } - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => CacheEngine.Implementation( - _syncCacheProvider, - _ttlStrategy, - _cacheKeyStrategy, - action, - context, - cancellationToken, - _onCacheGet, - _onCacheMiss, - _onCachePut, - _onCacheGetError, - _onCachePutError); -} \ No newline at end of file + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => CacheEngine.Implementation( + _syncCacheProvider, + _ttlStrategy, + _cacheKeyStrategy, + action, + context, + cancellationToken, + _onCacheGet, + _onCacheMiss, + _onCachePut, + _onCacheGetError, + _onCachePutError); + } +} diff --git a/src/Polly/Caching/CacheProviderExtensions.cs b/src/Polly/Caching/CacheProviderExtensions.cs index d39c68be5b4..3cfef047503 100644 --- a/src/Polly/Caching/CacheProviderExtensions.cs +++ b/src/Polly/Caching/CacheProviderExtensions.cs @@ -1,71 +1,72 @@ -namespace Polly.Caching; - -/// -/// Class that provides helper methods for configuring CacheProviders. -/// -public static class CacheProviderExtensions +namespace Polly.Caching { /// - /// Provides a strongly -typed version of the supplied + /// Class that provides helper methods for configuring CacheProviders. /// - /// The type the returned will handle. - /// The non-generic cache provider to wrap. - /// ISyncCacheProvider{TCacheFormat}. - public static ISyncCacheProvider For(this ISyncCacheProvider nonGenericCacheProvider) - => new GenericCacheProvider(nonGenericCacheProvider); + public static class CacheProviderExtensions + { + /// + /// Provides a strongly -typed version of the supplied + /// + /// The type the returned will handle. + /// The non-generic cache provider to wrap. + /// ISyncCacheProvider{TCacheFormat}. + public static ISyncCacheProvider For(this ISyncCacheProvider nonGenericCacheProvider) + => new GenericCacheProvider(nonGenericCacheProvider); - /// - /// Provides a strongly -typed version of the supplied - /// - /// The type the returned will handle. - /// The non-generic cache provider to wrap. - /// IAsyncCacheProvider{TCacheFormat}. - public static IAsyncCacheProvider AsyncFor(this IAsyncCacheProvider nonGenericCacheProvider) - => new AsyncGenericCacheProvider(nonGenericCacheProvider); + /// + /// Provides a strongly -typed version of the supplied + /// + /// The type the returned will handle. + /// The non-generic cache provider to wrap. + /// IAsyncCacheProvider{TCacheFormat}. + public static IAsyncCacheProvider AsyncFor(this IAsyncCacheProvider nonGenericCacheProvider) + => new AsyncGenericCacheProvider(nonGenericCacheProvider); - /// - /// Wraps the around the so that delegate return values of any type can be stored in the cache as type . - /// - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// A serializer which can serialize/deserialize all types to/from . - /// SerializingCacheProvider<TResult, TSerialized>. - public static SerializingCacheProvider WithSerializer( - this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new SerializingCacheProvider(cacheProvider, serializer); + /// + /// Wraps the around the so that delegate return values of any type can be stored in the cache as type . + /// + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// A serializer which can serialize/deserialize all types to/from . + /// SerializingCacheProvider<TResult, TSerialized>. + public static SerializingCacheProvider WithSerializer( + this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new SerializingCacheProvider(cacheProvider, serializer); - /// - /// Wraps the around the so that delegate return values of type can be stored in the cache as type . - /// - /// The return type of delegates which may be executed through the policy. - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// The serializer. - /// SerializingCacheProvider<TResult, TSerialized>. - public static SerializingCacheProvider WithSerializer( - this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new SerializingCacheProvider(cacheProvider, serializer); + /// + /// Wraps the around the so that delegate return values of type can be stored in the cache as type . + /// + /// The return type of delegates which may be executed through the policy. + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// The serializer. + /// SerializingCacheProvider<TResult, TSerialized>. + public static SerializingCacheProvider WithSerializer( + this ISyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new SerializingCacheProvider(cacheProvider, serializer); - /// - /// Wraps the around the asynchronous so that delegate return values of any type can be stored in the cache as type . - /// - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// A serializer which can serialize/deserialize all types to/from . - /// SerializingCacheProvider<TResult, TSerialized>. - public static AsyncSerializingCacheProvider WithSerializer( - this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new AsyncSerializingCacheProvider(cacheProvider, serializer); + /// + /// Wraps the around the asynchronous so that delegate return values of any type can be stored in the cache as type . + /// + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// A serializer which can serialize/deserialize all types to/from . + /// SerializingCacheProvider<TResult, TSerialized>. + public static AsyncSerializingCacheProvider WithSerializer( + this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new AsyncSerializingCacheProvider(cacheProvider, serializer); - /// - /// Wraps the around the asynchronous so that delegate return values of type can be stored in the cache as type . - /// - /// The return type of delegates which may be executed through the policy. - /// The type of serialized objects to be placed in the cache. - /// The cache provider. - /// The serializer. - /// SerializingCacheProvider<TResult, TSerialized>. - public static AsyncSerializingCacheProvider WithSerializer( - this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) - => new AsyncSerializingCacheProvider(cacheProvider, serializer); -} \ No newline at end of file + /// + /// Wraps the around the asynchronous so that delegate return values of type can be stored in the cache as type . + /// + /// The return type of delegates which may be executed through the policy. + /// The type of serialized objects to be placed in the cache. + /// The cache provider. + /// The serializer. + /// SerializingCacheProvider<TResult, TSerialized>. + public static AsyncSerializingCacheProvider WithSerializer( + this IAsyncCacheProvider cacheProvider, ICacheItemSerializer serializer) + => new AsyncSerializingCacheProvider(cacheProvider, serializer); + } +} diff --git a/src/Polly/Caching/CacheSyntax.cs b/src/Polly/Caching/CacheSyntax.cs index 5910e9c1d05..2d0551b4f73 100644 --- a/src/Polly/Caching/CacheSyntax.cs +++ b/src/Polly/Caching/CacheSyntax.cs @@ -1,339 +1,340 @@ using Polly.Caching; using System; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + public partial class Policy + { + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - onCacheError = onCacheError ?? ((_, _, _) => { }); - Action emptyDelegate = (_, _) => { }; + onCacheError = onCacheError ?? ((_, _, _) => { }); + Action emptyDelegate = (_, _) => { }; - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - /// - /// Builds a that will function like a result cache for delegate executions returning a result. - /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// - /// - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + /// + /// Builds a that will function like a result cache for delegate executions returning a result. + /// Before executing a delegate returning a result, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// + /// + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/CacheTResultSyntax.cs b/src/Polly/Caching/CacheTResultSyntax.cs index 957d2f74bb8..2896d6c8a45 100644 --- a/src/Polly/Caching/CacheTResultSyntax.cs +++ b/src/Polly/Caching/CacheTResultSyntax.cs @@ -1,827 +1,828 @@ using Polly.Caching; using System; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) + public partial class Policy { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - - return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key specified by . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, - emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, - onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, - emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) - { - onCacheError = onCacheError ?? ((_, _, _) => { }); - - Action emptyDelegate = (_, _) => { }; - - return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, - onCacheError, onCacheError); - } - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key. - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - ICacheKeyStrategy cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// Duration (ttl) for which to cache values. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - TimeSpan ttl, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - => Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, - onCachePut, onCacheGetError, onCachePutError); - - /// - /// Builds a that will function like a result cache for delegate executions returning a . - /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . - /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. - /// - /// - /// The cache provider. - /// A strategy for specifying ttl for values to be cached. - /// The cache key strategy. - /// Delegate to call on a cache hit, when value is returned from cache. - /// Delegate to call on a cache miss. - /// Delegate to call on cache put. - /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. - /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. - /// The policy instance. - /// cacheProvider - /// ttlStrategy - /// cacheKeyStrategy - /// onCacheGet - /// onCacheMiss - /// onCachePut - /// onCacheGetError - /// onCachePutError - public static CachePolicy Cache( - ISyncCacheProvider cacheProvider, - ITtlStrategy ttlStrategy, - Func cacheKeyStrategy, - Action onCacheGet, - Action onCacheMiss, - Action onCachePut, - Action onCacheGetError, - Action onCachePutError) - { - if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); - if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); - if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); - - if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); - if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); - if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); - - return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + + return Cache(cacheProvider.For(), ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key specified by . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Action onCacheError = null) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy.GetCacheKey, + emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, ICacheKeyStrategy cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, + onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, TimeSpan ttl, Func cacheKeyStrategy, Action onCacheError = null) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, + emptyDelegate, emptyDelegate, emptyDelegate, onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call if an exception is thrown when attempting to get a value from or put a value into the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + public static CachePolicy Cache(ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, Func cacheKeyStrategy, Action onCacheError = null) + { + onCacheError = onCacheError ?? ((_, _, _) => { }); + + Action emptyDelegate = (_, _) => { }; + + return Cache(cacheProvider, ttlStrategy, cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, + onCacheError, onCacheError); + } + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key. + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, DefaultCacheKeyStrategy.Instance.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + ICacheKeyStrategy cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// Duration (ttl) for which to cache values. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + TimeSpan ttl, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + => Cache(cacheProvider, ttlStrategy.For(), cacheKeyStrategy, onCacheGet, onCacheMiss, + onCachePut, onCacheGetError, onCachePutError); + + /// + /// Builds a that will function like a result cache for delegate executions returning a . + /// Before executing a delegate, checks whether the holds a value for the cache key determined by applying the to the execution . + /// If the provides a value from cache, returns that value and does not execute the governed delegate. If the does not provide a value, executes the governed delegate, stores the value with the , then returns the value. + /// + /// + /// The cache provider. + /// A strategy for specifying ttl for values to be cached. + /// The cache key strategy. + /// Delegate to call on a cache hit, when value is returned from cache. + /// Delegate to call on a cache miss. + /// Delegate to call on cache put. + /// Delegate to call if an exception is thrown when attempting to get a value from the cache, passing the execution context, the cache key, and the exception. + /// Delegate to call if an exception is thrown when attempting to put a value in the cache, passing the execution context, the cache key, and the exception. + /// The policy instance. + /// cacheProvider + /// ttlStrategy + /// cacheKeyStrategy + /// onCacheGet + /// onCacheMiss + /// onCachePut + /// onCacheGetError + /// onCachePutError + public static CachePolicy Cache( + ISyncCacheProvider cacheProvider, + ITtlStrategy ttlStrategy, + Func cacheKeyStrategy, + Action onCacheGet, + Action onCacheMiss, + Action onCachePut, + Action onCacheGetError, + Action onCachePutError) + { + if (cacheProvider == null) throw new ArgumentNullException(nameof(cacheProvider)); + if (ttlStrategy == null) throw new ArgumentNullException(nameof(ttlStrategy)); + if (cacheKeyStrategy == null) throw new ArgumentNullException(nameof(cacheKeyStrategy)); + + if (onCacheGet == null) throw new ArgumentNullException(nameof(onCacheGet)); + if (onCacheMiss == null) throw new ArgumentNullException(nameof(onCacheMiss)); + if (onCachePut == null) throw new ArgumentNullException(nameof(onCachePut)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + if (onCachePutError == null) throw new ArgumentNullException(nameof(onCachePutError)); + + return new CachePolicy(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/ContextualTtl.cs b/src/Polly/Caching/ContextualTtl.cs index d9f5a46b58b..d9844d94e4b 100644 --- a/src/Polly/Caching/ContextualTtl.cs +++ b/src/Polly/Caching/ContextualTtl.cs @@ -1,43 +1,44 @@ using System; -namespace Polly.Caching; - -/// -/// Defines a ttl strategy which will cache items for a TimeSpan which may be influenced by data in the execution context. -/// -public class ContextualTtl : ITtlStrategy +namespace Polly.Caching { /// - /// The key on the execution to use for storing the Ttl TimeSpan for which to cache. - /// - public static readonly string TimeSpanKey = "ContextualTtlTimeSpan"; - - /// - /// The key on the execution to use for storing whether the Ttl should be treated as sliding expiration. - /// If no value is provided for this key, a ttl will not be treated as sliding expiration. - /// - public static readonly string SlidingExpirationKey = "ContextualTtlSliding"; - - private static readonly Ttl _noTtl = new Ttl(TimeSpan.Zero, false); - - /// - /// Gets the TimeSpan for which to keep an item about to be cached, which may be influenced by data in the execution context. + /// Defines a ttl strategy which will cache items for a TimeSpan which may be influenced by data in the execution context. /// - /// The execution context. - /// The execution result. - /// TimeSpan. - public Ttl GetTtl(Context context, object result) + public class ContextualTtl : ITtlStrategy { - if (!context.ContainsKey(TimeSpanKey)) return _noTtl; + /// + /// The key on the execution to use for storing the Ttl TimeSpan for which to cache. + /// + public static readonly string TimeSpanKey = "ContextualTtlTimeSpan"; + + /// + /// The key on the execution to use for storing whether the Ttl should be treated as sliding expiration. + /// If no value is provided for this key, a ttl will not be treated as sliding expiration. + /// + public static readonly string SlidingExpirationKey = "ContextualTtlSliding"; + + private static readonly Ttl _noTtl = new Ttl(TimeSpan.Zero, false); + + /// + /// Gets the TimeSpan for which to keep an item about to be cached, which may be influenced by data in the execution context. + /// + /// The execution context. + /// The execution result. + /// TimeSpan. + public Ttl GetTtl(Context context, object result) + { + if (!context.ContainsKey(TimeSpanKey)) return _noTtl; - var sliding = false; + var sliding = false; - if (context.TryGetValue(SlidingExpirationKey, out var objValue)) - { - sliding = objValue as bool? ?? false; + if (context.TryGetValue(SlidingExpirationKey, out var objValue)) + { + sliding = objValue as bool? ?? false; + } + + return new Ttl(context[TimeSpanKey] as TimeSpan? ?? TimeSpan.Zero, sliding); } - return new Ttl(context[TimeSpanKey] as TimeSpan? ?? TimeSpan.Zero, sliding); } - -} \ No newline at end of file +} diff --git a/src/Polly/Caching/DefaultCacheKeyStrategy.cs b/src/Polly/Caching/DefaultCacheKeyStrategy.cs index 1c2b324664b..28a906c2a86 100644 --- a/src/Polly/Caching/DefaultCacheKeyStrategy.cs +++ b/src/Polly/Caching/DefaultCacheKeyStrategy.cs @@ -1,19 +1,20 @@ -namespace Polly.Caching; - -/// -/// The default cache key strategy for . Returns the property . -/// -public class DefaultCacheKeyStrategy : ICacheKeyStrategy +namespace Polly.Caching { /// - /// Gets the cache key from the given execution context. + /// The default cache key strategy for . Returns the property . /// - /// The execution context. - /// The cache key - public string GetCacheKey(Context context) => context.OperationKey; + public class DefaultCacheKeyStrategy : ICacheKeyStrategy + { + /// + /// Gets the cache key from the given execution context. + /// + /// The execution context. + /// The cache key + public string GetCacheKey(Context context) => context.OperationKey; - /// - /// Gets an instance of the . - /// - public static readonly ICacheKeyStrategy Instance = new DefaultCacheKeyStrategy(); -} \ No newline at end of file + /// + /// Gets an instance of the . + /// + public static readonly ICacheKeyStrategy Instance = new DefaultCacheKeyStrategy(); + } +} diff --git a/src/Polly/Caching/GenericCacheProvider.cs b/src/Polly/Caching/GenericCacheProvider.cs index 4c0fdba0d90..762323f8ffb 100644 --- a/src/Polly/Caching/GenericCacheProvider.cs +++ b/src/Polly/Caching/GenericCacheProvider.cs @@ -1,24 +1,25 @@ using System; -namespace Polly.Caching; - -/// -/// Provides a strongly-typed wrapper over a non-generic CacheProvider. -/// -/// The type of the objects in the cache. -internal class GenericCacheProvider : ISyncCacheProvider +namespace Polly.Caching { - private readonly ISyncCacheProvider _wrappedCacheProvider; + /// + /// Provides a strongly-typed wrapper over a non-generic CacheProvider. + /// + /// The type of the objects in the cache. + internal class GenericCacheProvider : ISyncCacheProvider + { + private readonly ISyncCacheProvider _wrappedCacheProvider; - internal GenericCacheProvider(ISyncCacheProvider nonGenericCacheProvider) - => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); + internal GenericCacheProvider(ISyncCacheProvider nonGenericCacheProvider) + => _wrappedCacheProvider = nonGenericCacheProvider ?? throw new ArgumentNullException(nameof(nonGenericCacheProvider)); - (bool, TCacheFormat) ISyncCacheProvider.TryGet(string key) - { - (var cacheHit, var result) = _wrappedCacheProvider.TryGet(key); - return (cacheHit, (TCacheFormat) (result ?? default(TCacheFormat))); - } + (bool, TCacheFormat) ISyncCacheProvider.TryGet(string key) + { + (var cacheHit, var result) = _wrappedCacheProvider.TryGet(key); + return (cacheHit, (TCacheFormat) (result ?? default(TCacheFormat))); + } - void ISyncCacheProvider.Put(string key, TCacheFormat value, Ttl ttl) - => _wrappedCacheProvider.Put(key, value, ttl); -} \ No newline at end of file + void ISyncCacheProvider.Put(string key, TCacheFormat value, Ttl ttl) + => _wrappedCacheProvider.Put(key, value, ttl); + } +} diff --git a/src/Polly/Caching/GenericTtlStrategy.cs b/src/Polly/Caching/GenericTtlStrategy.cs index 44a623534e7..e5fba9579e0 100644 --- a/src/Polly/Caching/GenericTtlStrategy.cs +++ b/src/Polly/Caching/GenericTtlStrategy.cs @@ -1,22 +1,23 @@ using System; -namespace Polly.Caching; - -/// -/// Represents a strongly-typed wrapper of a non-generic strategy. -/// -internal class GenericTtlStrategy : ITtlStrategy +namespace Polly.Caching { - private readonly ITtlStrategy _wrappedTtlStrategy; - - internal GenericTtlStrategy(ITtlStrategy ttlStrategy) - => _wrappedTtlStrategy = ttlStrategy ?? throw new ArgumentNullException(nameof(ttlStrategy)); - /// - /// Gets a TTL for a cacheable item, given the current execution context and result. + /// Represents a strongly-typed wrapper of a non-generic strategy. /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, TResult result) => _wrappedTtlStrategy.GetTtl(context, result); -} \ No newline at end of file + internal class GenericTtlStrategy : ITtlStrategy + { + private readonly ITtlStrategy _wrappedTtlStrategy; + + internal GenericTtlStrategy(ITtlStrategy ttlStrategy) + => _wrappedTtlStrategy = ttlStrategy ?? throw new ArgumentNullException(nameof(ttlStrategy)); + + /// + /// Gets a TTL for a cacheable item, given the current execution context and result. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + public Ttl GetTtl(Context context, TResult result) => _wrappedTtlStrategy.GetTtl(context, result); + } +} diff --git a/src/Polly/Caching/IAsyncCacheProvider.cs b/src/Polly/Caching/IAsyncCacheProvider.cs index 8ebaf4aae6a..31b4622fe4d 100644 --- a/src/Polly/Caching/IAsyncCacheProvider.cs +++ b/src/Polly/Caching/IAsyncCacheProvider.cs @@ -1,62 +1,63 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Caching; - -/// -/// Defines methods for classes providing asynchronous cache functionality for Polly s. -/// -public interface IAsyncCacheProvider +namespace Polly.Caching { /// - /// Gets a value from the cache asynchronously. + /// Defines methods for classes providing asynchronous cache functionality for Polly s. /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (null if not found). - /// - Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); + public interface IAsyncCacheProvider + { + /// + /// Gets a value from the cache asynchronously. + /// + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (null if not found). + /// + Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// A which completes when the value has been cached. - Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); -} + /// + /// Puts the specified value in the cache asynchronously. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// A which completes when the value has been cached. + Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); + } -/// -/// Defines methods for classes providing asynchronous cache functionality for Polly s. -/// -public interface IAsyncCacheProvider -{ /// - /// Gets a value from the cache asynchronously. + /// Defines methods for classes providing asynchronous cache functionality for Polly s. /// - /// The cache key. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// - /// A promising as Result a tuple whose first element is a value indicating whether - /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). - /// - Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); + public interface IAsyncCacheProvider + { + /// + /// Gets a value from the cache asynchronously. + /// + /// The cache key. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// + /// A promising as Result a tuple whose first element is a value indicating whether + /// the key was found in the cache, and whose second element is the value from the cache (default(TResult) if not found). + /// + Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext); - /// - /// Puts the specified value in the cache asynchronously. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - /// The cancellation token. - /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. - /// A which completes when the value has been cached. - Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); + /// + /// Puts the specified value in the cache asynchronously. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + /// The cancellation token. + /// Whether async calls should continue on a captured synchronization context.Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. + /// A which completes when the value has been cached. + Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext); + } } \ No newline at end of file diff --git a/src/Polly/Caching/ICacheItemSerializer.cs b/src/Polly/Caching/ICacheItemSerializer.cs index 48e8f68e024..3b8f39fef3b 100644 --- a/src/Polly/Caching/ICacheItemSerializer.cs +++ b/src/Polly/Caching/ICacheItemSerializer.cs @@ -1,24 +1,25 @@ -namespace Polly.Caching; - -/// -/// Defines operations for serializing and deserializing values being placed in caches by instances. -/// -/// The type of objects that this serializer can serialize. -/// The type of objects after serialization. -public interface ICacheItemSerializer +namespace Polly.Caching { /// - /// Serializes the specified object. + /// Defines operations for serializing and deserializing values being placed in caches by instances. /// - /// The object to serialize. - /// The serialized object - TSerialized Serialize(TResult objectToSerialize); + /// The type of objects that this serializer can serialize. + /// The type of objects after serialization. + public interface ICacheItemSerializer + { + /// + /// Serializes the specified object. + /// + /// The object to serialize. + /// The serialized object + TSerialized Serialize(TResult objectToSerialize); - /// - /// Deserializes the specified object. - /// - /// The object to deserialize. - /// The deserialized object - TResult Deserialize(TSerialized objectToDeserialize); + /// + /// Deserializes the specified object. + /// + /// The object to deserialize. + /// The deserialized object + TResult Deserialize(TSerialized objectToDeserialize); -} \ No newline at end of file + } +} diff --git a/src/Polly/Caching/ICacheKeyStrategy.cs b/src/Polly/Caching/ICacheKeyStrategy.cs index b81b9bc783f..bea3c94bee3 100644 --- a/src/Polly/Caching/ICacheKeyStrategy.cs +++ b/src/Polly/Caching/ICacheKeyStrategy.cs @@ -1,14 +1,15 @@ -namespace Polly.Caching; - -/// -/// Defines how a should get a string cache key from an execution -/// -public interface ICacheKeyStrategy +namespace Polly.Caching { /// - /// Gets the cache key from the given execution context. + /// Defines how a should get a string cache key from an execution /// - /// The execution context. - /// The cache key - string GetCacheKey(Context context); -} \ No newline at end of file + public interface ICacheKeyStrategy + { + /// + /// Gets the cache key from the given execution context. + /// + /// The execution context. + /// The cache key + string GetCacheKey(Context context); + } +} diff --git a/src/Polly/Caching/ICachePolicy.cs b/src/Polly/Caching/ICachePolicy.cs index 009fa234019..304b8c0cf0b 100644 --- a/src/Polly/Caching/ICachePolicy.cs +++ b/src/Polly/Caching/ICachePolicy.cs @@ -1,15 +1,17 @@ -namespace Polly.Caching; - -/// -/// Defines properties and methods common to all Cache policies. -/// -public interface ICachePolicy : IsPolicy +namespace Polly.Caching { -} + /// + /// Defines properties and methods common to all Cache policies. + /// -/// -/// Defines properties and methods common to all Cache policies generic-typed for executions returning results of type . -/// -public interface ICachePolicy : ICachePolicy -{ -} \ No newline at end of file + public interface ICachePolicy : IsPolicy + { + } + + /// + /// Defines properties and methods common to all Cache policies generic-typed for executions returning results of type . + /// + public interface ICachePolicy : ICachePolicy + { + } +} diff --git a/src/Polly/Caching/ISyncCacheProvider.cs b/src/Polly/Caching/ISyncCacheProvider.cs index 7500f0f1c5d..15390ee029d 100644 --- a/src/Polly/Caching/ISyncCacheProvider.cs +++ b/src/Polly/Caching/ISyncCacheProvider.cs @@ -1,49 +1,50 @@ -namespace Polly.Caching; - -/// -/// Defines methods for classes providing synchronous cache functionality for Polly s. -/// -public interface ISyncCacheProvider +namespace Polly.Caching { /// - /// Gets a value from cache. + /// Defines methods for classes providing synchronous cache functionality for Polly s. /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (null if not found). - /// - (bool, object) TryGet(string key); + public interface ISyncCacheProvider + { + /// + /// Gets a value from cache. + /// + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (null if not found). + /// + (bool, object) TryGet(string key); - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - void Put(string key, object value, Ttl ttl); -} + /// + /// Puts the specified value in the cache. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + void Put(string key, object value, Ttl ttl); + } -/// -/// Defines methods for classes providing synchronous cache functionality for Polly s. -/// -public interface ISyncCacheProvider -{ /// - /// Gets a value from cache. + /// Defines methods for classes providing synchronous cache functionality for Polly s. /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (default(TResult) if not found). - /// - (bool, TResult) TryGet(string key); + public interface ISyncCacheProvider + { + /// + /// Gets a value from cache. + /// + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (default(TResult) if not found). + /// + (bool, TResult) TryGet(string key); - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - void Put(string key, TResult value, Ttl ttl); + /// + /// Puts the specified value in the cache. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + void Put(string key, TResult value, Ttl ttl); + } } \ No newline at end of file diff --git a/src/Polly/Caching/ITtlStrategy.cs b/src/Polly/Caching/ITtlStrategy.cs index 929b5fa26df..dbb1122deb1 100644 --- a/src/Polly/Caching/ITtlStrategy.cs +++ b/src/Polly/Caching/ITtlStrategy.cs @@ -1,23 +1,24 @@ -namespace Polly.Caching; - -/// -/// Defines a strategy for providing time-to-live durations for cacheable results. -/// -public interface ITtlStrategy : ITtlStrategy +namespace Polly.Caching { + /// + /// Defines a strategy for providing time-to-live durations for cacheable results. + /// + public interface ITtlStrategy : ITtlStrategy + { -} + } -/// -/// Defines a strategy for providing time-to-live durations for cacheable results. -/// -public interface ITtlStrategy -{ /// - /// Gets a TTL for a cacheable item, given the current execution context. + /// Defines a strategy for providing time-to-live durations for cacheable results. /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - Ttl GetTtl(Context context, TResult result); -} \ No newline at end of file + public interface ITtlStrategy + { + /// + /// Gets a TTL for a cacheable item, given the current execution context. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + Ttl GetTtl(Context context, TResult result); + } +} diff --git a/src/Polly/Caching/NonSlidingTtl.cs b/src/Polly/Caching/NonSlidingTtl.cs index c96e3b9afcd..91f48e19221 100644 --- a/src/Polly/Caching/NonSlidingTtl.cs +++ b/src/Polly/Caching/NonSlidingTtl.cs @@ -1,35 +1,36 @@ using System; using Polly.Utilities; -namespace Polly.Caching; - -/// -/// Represents an expiring at an absolute time, not with sliding expiration. -/// -public abstract class NonSlidingTtl : ITtlStrategy +namespace Polly.Caching { /// - /// The absolute expiration time for cache items, represented by this strategy. + /// Represents an expiring at an absolute time, not with sliding expiration. /// - protected readonly DateTimeOffset absoluteExpirationTime; + public abstract class NonSlidingTtl : ITtlStrategy + { + /// + /// The absolute expiration time for cache items, represented by this strategy. + /// + protected readonly DateTimeOffset absoluteExpirationTime; - /// - /// Constructs a new instance of the strategy. - /// - /// The absolute expiration time for cache items, represented by this strategy. - protected NonSlidingTtl(DateTimeOffset absoluteExpirationTime) - => this.absoluteExpirationTime = absoluteExpirationTime; + /// + /// Constructs a new instance of the strategy. + /// + /// The absolute expiration time for cache items, represented by this strategy. + protected NonSlidingTtl(DateTimeOffset absoluteExpirationTime) + => this.absoluteExpirationTime = absoluteExpirationTime; - /// - /// Gets a TTL for a cacheable item, given the current execution context. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, object result) - { - var untilPointInTime = absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow()); - var remaining = untilPointInTime > TimeSpan.Zero ? untilPointInTime : TimeSpan.Zero; - return new Ttl(remaining, false); + /// + /// Gets a TTL for a cacheable item, given the current execution context. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + public Ttl GetTtl(Context context, object result) + { + var untilPointInTime = absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow()); + var remaining = untilPointInTime > TimeSpan.Zero ? untilPointInTime : TimeSpan.Zero; + return new Ttl(remaining, false); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/RelativeTtl.cs b/src/Polly/Caching/RelativeTtl.cs index 6b8bb50bcbb..2a2dcf34383 100644 --- a/src/Polly/Caching/RelativeTtl.cs +++ b/src/Polly/Caching/RelativeTtl.cs @@ -1,30 +1,31 @@ using System; -namespace Polly.Caching; - -/// -/// Defines a ttl strategy which will cache items for the specified time. -/// -public class RelativeTtl : ITtlStrategy +namespace Polly.Caching { - private readonly TimeSpan ttl; - /// - /// Initializes a new instance of the class. + /// Defines a ttl strategy which will cache items for the specified time. /// - /// The timespan for which to consider the cache item valid. - public RelativeTtl(TimeSpan ttl) + public class RelativeTtl : ITtlStrategy { - if (ttl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(ttl), "The ttl for items to cache must be greater than zero."); + private readonly TimeSpan ttl; - this.ttl = ttl; - } + /// + /// Initializes a new instance of the class. + /// + /// The timespan for which to consider the cache item valid. + public RelativeTtl(TimeSpan ttl) + { + if (ttl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(ttl), "The ttl for items to cache must be greater than zero."); - /// - /// Gets a TTL for a cacheable item, given the current execution context. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. - public Ttl GetTtl(Context context, object result) => new Ttl(ttl); -} \ No newline at end of file + this.ttl = ttl; + } + + /// + /// Gets a TTL for a cacheable item, given the current execution context. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + public Ttl GetTtl(Context context, object result) => new Ttl(ttl); + } +} diff --git a/src/Polly/Caching/ResultTtl.cs b/src/Polly/Caching/ResultTtl.cs index f5f7567988b..cd933add8de 100644 --- a/src/Polly/Caching/ResultTtl.cs +++ b/src/Polly/Caching/ResultTtl.cs @@ -1,38 +1,39 @@ using System; -namespace Polly.Caching; - -/// -/// Defines a ttl strategy which can calculate a duration to cache items dynamically based on the execution context and result of the execution. -/// -/// The type of results that the ttl calculation function will take as an input parameter. -public class ResultTtl : ITtlStrategy +namespace Polly.Caching { - private readonly Func _ttlFunc; - /// - /// Constructs a new instance of the ttl strategy, with a func calculating based on the value to cache. + /// Defines a ttl strategy which can calculate a duration to cache items dynamically based on the execution context and result of the execution. /// - /// The function to calculate the TTL for which cache items should be considered valid. - public ResultTtl(Func ttlFunc) + /// The type of results that the ttl calculation function will take as an input parameter. + public class ResultTtl : ITtlStrategy { - if (ttlFunc == null) throw new ArgumentNullException(nameof(ttlFunc)); - _ttlFunc = (_, result) => ttlFunc(result); - } + private readonly Func _ttlFunc; - /// - /// Constructs a new instance of the ttl strategy, with a func calculating based on the execution and value to cache. - /// - /// The function to calculate the TTL for which cache items should be considered valid. - public ResultTtl(Func ttlFunc) - => _ttlFunc = ttlFunc ?? throw new ArgumentNullException(nameof(ttlFunc)); + /// + /// Constructs a new instance of the ttl strategy, with a func calculating based on the value to cache. + /// + /// The function to calculate the TTL for which cache items should be considered valid. + public ResultTtl(Func ttlFunc) + { + if (ttlFunc == null) throw new ArgumentNullException(nameof(ttlFunc)); + _ttlFunc = (_, result) => ttlFunc(result); + } - /// - /// Gets a TTL for the cacheable item. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. + /// + /// Constructs a new instance of the ttl strategy, with a func calculating based on the execution and value to cache. + /// + /// The function to calculate the TTL for which cache items should be considered valid. + public ResultTtl(Func ttlFunc) + => _ttlFunc = ttlFunc ?? throw new ArgumentNullException(nameof(ttlFunc)); - public Ttl GetTtl(Context context, TResult result) => _ttlFunc(context, result); -} \ No newline at end of file + /// + /// Gets a TTL for the cacheable item. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + + public Ttl GetTtl(Context context, TResult result) => _ttlFunc(context, result); + } +} diff --git a/src/Polly/Caching/SerializingCacheProvider.cs b/src/Polly/Caching/SerializingCacheProvider.cs index 5a9571fb747..6a32e84ed34 100644 --- a/src/Polly/Caching/SerializingCacheProvider.cs +++ b/src/Polly/Caching/SerializingCacheProvider.cs @@ -1,102 +1,103 @@ using System; -namespace Polly.Caching; - -/// -/// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with synchronous . -/// -/// The type of serialized objects to be placed in the cache. -public class SerializingCacheProvider : ISyncCacheProvider +namespace Polly.Caching { - private readonly ISyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; - /// - /// Initializes a new instance of the class. + /// Defines an which serializes objects of any type in and out of an underlying cache which caches as type . For use with synchronous . /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) + /// The type of serialized objects to be placed in the cache. + public class SerializingCacheProvider : ISyncCacheProvider { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - } + private readonly ISyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; - /// - /// Gets a value from the cache. - /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (null if not found). - /// - public (bool, object) TryGet(string key) - { - (var cacheHit, var objectToDeserialize) = _wrappedCacheProvider.TryGet(key); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); - } + /// + /// Initializes a new instance of the class. + /// + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) + { + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - public void Put(string key, object value, Ttl ttl) - { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); - } - -} + /// + /// Gets a value from the cache. + /// + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (null if not found). + /// + public (bool, object) TryGet(string key) + { + (var cacheHit, var objectToDeserialize) = _wrappedCacheProvider.TryGet(key); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); + } -/// -/// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with synchronous . -/// -/// The return type of delegates which may be executed through the policy. -/// The type of serialized objects to be placed in the cache. -public class SerializingCacheProvider : ISyncCacheProvider -{ - private readonly ISyncCacheProvider _wrappedCacheProvider; - private readonly ICacheItemSerializer _serializer; + /// + /// Puts the specified value in the cache. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + public void Put(string key, object value, Ttl ttl) + { + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); + } - /// - /// Initializes a new instance of the class. - /// - /// The wrapped cache provider. - /// The serializer. - /// wrappedCacheProvider - /// serializer - public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) - { - _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// - /// Gets a value from the cache. + /// Defines an which serializes objects of type in and out of an underlying cache which caches as type . For use with synchronous . /// - /// The cache key. - /// - /// A tuple whose first element is a value indicating whether the key was found in the cache, - /// and whose second element is the value from the cache (default(TResult) if not found). - /// - public (bool, TResult) TryGet(string key) + /// The return type of delegates which may be executed through the policy. + /// The type of serialized objects to be placed in the cache. + public class SerializingCacheProvider : ISyncCacheProvider { - (var cacheHit, var objectToDeserialize) = _wrappedCacheProvider.TryGet(key); - return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); - } + private readonly ISyncCacheProvider _wrappedCacheProvider; + private readonly ICacheItemSerializer _serializer; - /// - /// Puts the specified value in the cache. - /// - /// The cache key. - /// The value to put into the cache. - /// The time-to-live for the cache entry. - public void Put(string key, TResult value, Ttl ttl) - { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); - } + /// + /// Initializes a new instance of the class. + /// + /// The wrapped cache provider. + /// The serializer. + /// wrappedCacheProvider + /// serializer + public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) + { + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + } + + /// + /// Gets a value from the cache. + /// + /// The cache key. + /// + /// A tuple whose first element is a value indicating whether the key was found in the cache, + /// and whose second element is the value from the cache (default(TResult) if not found). + /// + public (bool, TResult) TryGet(string key) + { + (var cacheHit, var objectToDeserialize) = _wrappedCacheProvider.TryGet(key); + return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); + } -} \ No newline at end of file + /// + /// Puts the specified value in the cache. + /// + /// The cache key. + /// The value to put into the cache. + /// The time-to-live for the cache entry. + public void Put(string key, TResult value, Ttl ttl) + { + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); + } + + } +} diff --git a/src/Polly/Caching/SlidingTtl.cs b/src/Polly/Caching/SlidingTtl.cs index 78ca9cae16d..c203480380b 100644 --- a/src/Polly/Caching/SlidingTtl.cs +++ b/src/Polly/Caching/SlidingTtl.cs @@ -1,31 +1,33 @@ using System; -namespace Polly.Caching; - -/// -/// Defines a ttl strategy which will cache items with a sliding ttl. -/// -public class SlidingTtl : ITtlStrategy +namespace Polly.Caching { - private readonly Ttl ttl; - /// - /// Constructs a new instance of the ttl strategy. + /// Defines a ttl strategy which will cache items with a sliding ttl. /// - /// The sliding timespan for which cache items should be considered valid. - public SlidingTtl(TimeSpan slidingTtl) + + public class SlidingTtl : ITtlStrategy { - if (slidingTtl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(slidingTtl), "The ttl for items to cache must be greater than zero."); + private readonly Ttl ttl; - ttl = new Ttl(slidingTtl, true); - } + /// + /// Constructs a new instance of the ttl strategy. + /// + /// The sliding timespan for which cache items should be considered valid. + public SlidingTtl(TimeSpan slidingTtl) + { + if (slidingTtl < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(slidingTtl), "The ttl for items to cache must be greater than zero."); - /// - /// Gets a TTL for the cacheable item. - /// - /// The execution context. - /// The execution result. - /// A representing the remaining Ttl of the cached item. + ttl = new Ttl(slidingTtl, true); + } - public Ttl GetTtl(Context context, object result) => ttl; -} \ No newline at end of file + /// + /// Gets a TTL for the cacheable item. + /// + /// The execution context. + /// The execution result. + /// A representing the remaining Ttl of the cached item. + + public Ttl GetTtl(Context context, object result) => ttl; + } +} diff --git a/src/Polly/Caching/Ttl.cs b/src/Polly/Caching/Ttl.cs index 214704e9066..5618917fdbc 100644 --- a/src/Polly/Caching/Ttl.cs +++ b/src/Polly/Caching/Ttl.cs @@ -1,39 +1,40 @@ using System; -namespace Polly.Caching; - -/// -/// Represents a time-to-live for a given cache item. -/// -public struct Ttl +namespace Polly.Caching { /// - /// The timespan for which this cache-item remains valid. + /// Represents a time-to-live for a given cache item. /// - public TimeSpan Timespan; + public struct Ttl + { + /// + /// The timespan for which this cache-item remains valid. + /// + public TimeSpan Timespan; - /// - /// Whether this should be considered as sliding expiration: that is, the cache item should be considered valid for a further period of duration each time the cache item is retrieved. - /// - public bool SlidingExpiration; + /// + /// Whether this should be considered as sliding expiration: that is, the cache item should be considered valid for a further period of duration each time the cache item is retrieved. + /// + public bool SlidingExpiration; - /// - /// Creates a new struct. - /// - /// The timespan for which this cache-item remains valid. - /// Will be considered as not denoting sliding expiration. - public Ttl(TimeSpan timeSpan) : this(timeSpan, false) - { - } + /// + /// Creates a new struct. + /// + /// The timespan for which this cache-item remains valid. + /// Will be considered as not denoting sliding expiration. + public Ttl(TimeSpan timeSpan) : this(timeSpan, false) + { + } - /// - /// Creates a new struct. - /// - /// The timespan for which this cache-item remains valid - /// Whether this should be considered as sliding expiration. - public Ttl(TimeSpan timeSpan, bool slidingExpiration) - { - Timespan = timeSpan; - SlidingExpiration = slidingExpiration; + /// + /// Creates a new struct. + /// + /// The timespan for which this cache-item remains valid + /// Whether this should be considered as sliding expiration. + public Ttl(TimeSpan timeSpan, bool slidingExpiration) + { + Timespan = timeSpan; + SlidingExpiration = slidingExpiration; + } } -} \ No newline at end of file +} diff --git a/src/Polly/Caching/TtlStrategyExtensions.cs b/src/Polly/Caching/TtlStrategyExtensions.cs index 58a51688429..4c742664d5e 100644 --- a/src/Polly/Caching/TtlStrategyExtensions.cs +++ b/src/Polly/Caching/TtlStrategyExtensions.cs @@ -1,16 +1,17 @@ -namespace Polly.Caching; - -/// -/// Class that provides helper methods for configuring TtlStrategies. -/// -internal static class TtlStrategyExtensions +namespace Polly.Caching { /// - /// Provides a strongly -typed version of the supplied + /// Class that provides helper methods for configuring TtlStrategies. /// - /// The type the returned will handle. - /// The non-generic ttl strategy to wrap. - /// ITtlStrategy{TCacheFormat}. - internal static ITtlStrategy For(this ITtlStrategy ttlStrategy) - => new GenericTtlStrategy(ttlStrategy); -} \ No newline at end of file + internal static class TtlStrategyExtensions + { + /// + /// Provides a strongly -typed version of the supplied + /// + /// The type the returned will handle. + /// The non-generic ttl strategy to wrap. + /// ITtlStrategy{TCacheFormat}. + internal static ITtlStrategy For(this ITtlStrategy ttlStrategy) + => new GenericTtlStrategy(ttlStrategy); + } +} diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs index aad811522b3..8ac0ddb82a3 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs @@ -2,249 +2,250 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class AdvancedCircuitBreakerSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + public static class AdvancedCircuitBreakerSyntax { - Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + { + Action doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs index b6b2d21d1db..d04c90f103b 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs @@ -2,251 +2,252 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class AdvancedCircuitBreakerTResultSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + public static class AdvancedCircuitBreakerTResultSyntax { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure). + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + { + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The return type of delegates which may be executed through the policy. - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreaker( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The return type of delegates which may be executed through the policy. + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreaker( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The return type of delegates which may be executed through the policy. - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - /// (see "Release It!" by Michael T. Nygard fi) - public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The return type of delegates which may be executed through the policy. + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + /// (see "Release It!" by Michael T. Nygard fi) + public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitController.cs b/src/Polly/CircuitBreaker/AdvancedCircuitController.cs index 27e45aacd06..aa0d5cae56a 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitController.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitController.cs @@ -1,104 +1,105 @@ using System; using Polly.Utilities; -namespace Polly.CircuitBreaker; - -internal class AdvancedCircuitController : CircuitStateController +namespace Polly.CircuitBreaker { - private const short NumberOfWindows = 10; - internal static readonly long ResolutionOfCircuitTimer = TimeSpan.FromMilliseconds(20).Ticks; - - private readonly IHealthMetrics _metrics; - private readonly double _failureThreshold; - private readonly int _minimumThroughput; - - public AdvancedCircuitController( - double failureThreshold, - TimeSpan samplingDuration, - int minimumThroughput, - TimeSpan durationOfBreak, - Action, CircuitState, TimeSpan, Context> onBreak, - Action onReset, - Action onHalfOpen - ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) + internal class AdvancedCircuitController : CircuitStateController { - _metrics = samplingDuration.Ticks < ResolutionOfCircuitTimer * NumberOfWindows - ? (IHealthMetrics)new SingleHealthMetrics(samplingDuration) - : (IHealthMetrics)new RollingHealthMetrics(samplingDuration, NumberOfWindows); + private const short NumberOfWindows = 10; + internal static readonly long ResolutionOfCircuitTimer = TimeSpan.FromMilliseconds(20).Ticks; + + private readonly IHealthMetrics _metrics; + private readonly double _failureThreshold; + private readonly int _minimumThroughput; + + public AdvancedCircuitController( + double failureThreshold, + TimeSpan samplingDuration, + int minimumThroughput, + TimeSpan durationOfBreak, + Action, CircuitState, TimeSpan, Context> onBreak, + Action onReset, + Action onHalfOpen + ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) + { + _metrics = samplingDuration.Ticks < ResolutionOfCircuitTimer * NumberOfWindows + ? (IHealthMetrics)new SingleHealthMetrics(samplingDuration) + : (IHealthMetrics)new RollingHealthMetrics(samplingDuration, NumberOfWindows); - _failureThreshold = failureThreshold; - _minimumThroughput = minimumThroughput; - } + _failureThreshold = failureThreshold; + _minimumThroughput = minimumThroughput; + } - public override void OnCircuitReset(Context context) - { - using (TimedLock.Lock(_lock)) + public override void OnCircuitReset(Context context) { - // Is only null during initialization of the current class - // as the variable is not set, before the base class calls - // current method from constructor. - _metrics?.Reset_NeedsLock(); + using (TimedLock.Lock(_lock)) + { + // Is only null during initialization of the current class + // as the variable is not set, before the base class calls + // current method from constructor. + _metrics?.Reset_NeedsLock(); - ResetInternal_NeedsLock(context); + ResetInternal_NeedsLock(context); + } } - } - public override void OnActionSuccess(Context context) - { - using (TimedLock.Lock(_lock)) + public override void OnActionSuccess(Context context) { - switch (_circuitState) + using (TimedLock.Lock(_lock)) { - case CircuitState.HalfOpen: - OnCircuitReset(context); - break; + switch (_circuitState) + { + case CircuitState.HalfOpen: + OnCircuitReset(context); + break; - case CircuitState.Closed: - break; + case CircuitState.Closed: + break; - case CircuitState.Open: - case CircuitState.Isolated: - break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no special action; only time passing governs transitioning from Open to HalfOpen state. + case CircuitState.Open: + case CircuitState.Isolated: + break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no special action; only time passing governs transitioning from Open to HalfOpen state. - default: - throw new InvalidOperationException("Unhandled CircuitState."); - } + default: + throw new InvalidOperationException("Unhandled CircuitState."); + } - _metrics.IncrementSuccess_NeedsLock(); + _metrics.IncrementSuccess_NeedsLock(); + } } - } - public override void OnActionFailure(DelegateResult outcome, Context context) - { - using (TimedLock.Lock(_lock)) + public override void OnActionFailure(DelegateResult outcome, Context context) { - _lastOutcome = outcome; - - switch (_circuitState) + using (TimedLock.Lock(_lock)) { - case CircuitState.HalfOpen: - Break_NeedsLock(context); - return; - - case CircuitState.Closed: - _metrics.IncrementFailure_NeedsLock(); - var healthCount = _metrics.GetHealthCount_NeedsLock(); + _lastOutcome = outcome; - var throughput = healthCount.Total; - if (throughput >= _minimumThroughput && ((double)healthCount.Failures) / throughput >= _failureThreshold) - { + switch (_circuitState) + { + case CircuitState.HalfOpen: Break_NeedsLock(context); - } - break; - - case CircuitState.Open: - case CircuitState.Isolated: - _metrics.IncrementFailure_NeedsLock(); - break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action beyond tracking the metric; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). - - default: - throw new InvalidOperationException("Unhandled CircuitState."); + return; + + case CircuitState.Closed: + _metrics.IncrementFailure_NeedsLock(); + var healthCount = _metrics.GetHealthCount_NeedsLock(); + + var throughput = healthCount.Total; + if (throughput >= _minimumThroughput && ((double)healthCount.Failures) / throughput >= _failureThreshold) + { + Break_NeedsLock(context); + } + break; + + case CircuitState.Open: + case CircuitState.Isolated: + _metrics.IncrementFailure_NeedsLock(); + break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action beyond tracking the metric; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). + + default: + throw new InvalidOperationException("Unhandled CircuitState."); + } } } } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs index 073864a72d8..f5b6336de4e 100644 --- a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs @@ -2,253 +2,254 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class AsyncAdvancedCircuitBreakerSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + public static class AsyncAdvancedCircuitBreakerSyntax { - Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + { + Action doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs index 4dcb9879877..e339b9960d7 100644 --- a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs @@ -2,252 +2,253 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class AsyncAdvancedCircuitBreakerTResultSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + public static class AsyncAdvancedCircuitBreakerTResultSyntax { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception or result exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) + { + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.AdvancedCircuitBreakerAsync( - failureThreshold, samplingDuration, minimumThroughput, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.AdvancedCircuitBreakerAsync( + failureThreshold, samplingDuration, minimumThroughput, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. - /// The duration of the timeslice over which failure ratios are assessed. - /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// failureThreshold;Value must be greater than zero - /// failureThreshold;Value must be less than or equal to one - /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer - /// minimumThroughput;Value must be greater than one - /// durationOfBreak;Value must be greater than zero - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if, within any timeslice of duration , the proportion of actions resulting in a handled exception exceeds , provided also that the number of actions through the circuit in the timeslice is at least . + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The failure threshold at which the circuit will break (a number between 0 and 1; eg 0.5 represents breaking if 50% or more of actions result in a handled failure. + /// The duration of the timeslice over which failure ratios are assessed. + /// The minimum throughput: this many actions or more must pass through the circuit in the timeslice, for statistics to be considered significant and the circuit-breaker to come into action. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// failureThreshold;Value must be greater than zero + /// failureThreshold;Value must be less than or equal to one + /// samplingDuration;Value must be equal to or greater than the minimum resolution of the CircuitBreaker timer + /// minimumThroughput;Value must be greater than one + /// durationOfBreak;Value must be greater than zero + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + var resolutionOfCircuit = TimeSpan.FromTicks(AdvancedCircuitController.ResolutionOfCircuitTimer); - if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); - if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); - if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); - if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be greater than zero."); + if (failureThreshold > 1) throw new ArgumentOutOfRangeException(nameof(failureThreshold), "Value must be less than or equal to one."); + if (samplingDuration < resolutionOfCircuit) throw new ArgumentOutOfRangeException(nameof(samplingDuration), $"Value must be equal to or greater than {resolutionOfCircuit.TotalMilliseconds} milliseconds. This is the minimum resolution of the CircuitBreaker timer."); + if (minimumThroughput <= 1) throw new ArgumentOutOfRangeException(nameof(minimumThroughput), "Value must be greater than one."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new AdvancedCircuitController( - failureThreshold, - samplingDuration, - minimumThroughput, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); + var breakerController = new AdvancedCircuitController( + failureThreshold, + samplingDuration, + minimumThroughput, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } } \ No newline at end of file diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs index 76ca78aef00..38c690c9a63 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs @@ -3,50 +3,52 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.CircuitBreaker; - -internal class AsyncCircuitBreakerEngine +namespace Polly.CircuitBreaker { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - ICircuitController breakerController) + internal class AsyncCircuitBreakerEngine { - cancellationToken.ThrowIfCancellationRequested(); - - breakerController.OnActionPreExecute(); - - try + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + ICircuitController breakerController) { - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + cancellationToken.ThrowIfCancellationRequested(); - if (shouldHandleResultPredicates.AnyMatch(result)) + breakerController.OnActionPreExecute(); + + try { - breakerController.OnActionFailure(new DelegateResult(result), context); + var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + + if (shouldHandleResultPredicates.AnyMatch(result)) + { + breakerController.OnActionFailure(new DelegateResult(result), context); + } + else + { + breakerController.OnActionSuccess(context); + } + + return result; } - else + catch (Exception ex) { - breakerController.OnActionSuccess(context); - } + var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - return result; - } - catch (Exception ex) - { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { + breakerController.OnActionFailure(new DelegateResult(handledException), context); + + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); throw; } - - breakerController.OnActionFailure(new DelegateResult(handledException), context); - - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); - throw; } } -} \ No newline at end of file +} + diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs index 6f6de525714..846ee670543 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerPolicy.cs @@ -4,114 +4,115 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.CircuitBreaker; - -/// -/// A circuit-breaker policy that can be applied to async delegates. -/// -public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy +namespace Polly.CircuitBreaker { - internal readonly ICircuitController _breakerController; - - internal AsyncCircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - { - _breakerController = breakerController; - } - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - public Exception LastException => _breakerController.LastException; - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// A circuit-breaker policy that can be applied to async delegates. /// - public void Isolate() => _breakerController.Isolate(); + public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy + { + internal readonly ICircuitController _breakerController; + + internal AsyncCircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) + { + _breakerController = breakerController; + } + + /// + /// Gets the state of the underlying circuit. + /// + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + public Exception LastException => _breakerController.LastException; + + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + protected override async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + TResult result = default; + await AsyncCircuitBreakerEngine.ImplementationAsync( + async (ctx, ct) => { result = await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + continueOnCapturedContext, + ExceptionPredicates, + ResultPredicates.None, + _breakerController).ConfigureAwait(continueOnCapturedContext); + return result; + } + } /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// A circuit-breaker policy that can be applied to async delegates. /// - public void Reset() => _breakerController.Reset(); - - /// - protected override async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) + /// The return type of delegates which may be executed through the policy. + public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy { - TResult result = default; - await AsyncCircuitBreakerEngine.ImplementationAsync( - async (ctx, ct) => { result = await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, - cancellationToken, - continueOnCapturedContext, - ExceptionPredicates, - ResultPredicates.None, - _breakerController).ConfigureAwait(continueOnCapturedContext); - return result; + internal readonly ICircuitController _breakerController; + + internal AsyncCircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) + { + _breakerController = breakerController; + } + + /// + /// Gets the state of the underlying circuit. + /// + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + public Exception LastException => _breakerController.LastException; + + /// + /// Gets the last result returned from a user delegate which the circuit-breaker handled. + /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. + /// + public TResult LastHandledResult => _breakerController.LastHandledResult; + + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncCircuitBreakerEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + ExceptionPredicates, + ResultPredicates, + _breakerController); } } - -/// -/// A circuit-breaker policy that can be applied to async delegates. -/// -/// The return type of delegates which may be executed through the policy. -public class AsyncCircuitBreakerPolicy : AsyncPolicy, ICircuitBreakerPolicy -{ - internal readonly ICircuitController _breakerController; - - internal AsyncCircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - { - _breakerController = breakerController; - } - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - public Exception LastException => _breakerController.LastException; - - /// - /// Gets the last result returned from a user delegate which the circuit-breaker handled. - /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. - /// - public TResult LastHandledResult => _breakerController.LastHandledResult; - - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - public void Isolate() => _breakerController.Isolate(); - - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - public void Reset() => _breakerController.Reset(); - - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncCircuitBreakerEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - ExceptionPredicates, - ResultPredicates, - _breakerController); -} \ No newline at end of file diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs index 76ad3e4a787..fdeda3b388d 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs @@ -2,215 +2,217 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class AsyncCircuitBreakerSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) + public static class AsyncCircuitBreakerSyntax { - Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) + { + Action doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new ConsecutiveCountCircuitController( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); + var breakerController = new ConsecutiveCountCircuitController( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } -} \ No newline at end of file +} + diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs index d181b12ac94..08024da3f68 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs @@ -1,215 +1,217 @@ using System; using Polly.CircuitBreaker; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class AsyncCircuitBreakerTResultSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) + public static class AsyncCircuitBreakerTResultSyntax { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) + { + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreakerAsync( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new ConsecutiveCountCircuitController( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new AsyncCircuitBreakerPolicy( - policyBuilder, - breakerController - ); + var breakerController = new ConsecutiveCountCircuitController( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new AsyncCircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } -} \ No newline at end of file +} + diff --git a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs index 9e637caf60c..1629da85111 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs @@ -2,52 +2,53 @@ using System.Runtime.ExceptionServices; using System.Threading; -namespace Polly.CircuitBreaker; - -internal class CircuitBreakerEngine +namespace Polly.CircuitBreaker { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - ICircuitController breakerController) + internal class CircuitBreakerEngine { - cancellationToken.ThrowIfCancellationRequested(); - - breakerController.OnActionPreExecute(); - - try + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + ICircuitController breakerController) { - var result = action(context, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); - if (shouldHandleResultPredicates.AnyMatch(result)) - { - breakerController.OnActionFailure(new DelegateResult(result), context); - } - else - { - breakerController.OnActionSuccess(context); - } + breakerController.OnActionPreExecute(); - return result; - } - catch (Exception ex) - { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) + try { - throw; + var result = action(context, cancellationToken); + + if (shouldHandleResultPredicates.AnyMatch(result)) + { + breakerController.OnActionFailure(new DelegateResult(result), context); + } + else + { + breakerController.OnActionSuccess(context); + } + + return result; } - - breakerController.OnActionFailure(new DelegateResult(handledException), context); - - if (handledException != ex) + catch (Exception ex) { - ExceptionDispatchInfo.Capture(handledException).Throw(); + var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } + + breakerController.OnActionFailure(new DelegateResult(handledException), context); + + if (handledException != ex) + { + ExceptionDispatchInfo.Capture(handledException).Throw(); + } + throw; } - throw; } } } \ No newline at end of file diff --git a/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs b/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs index 0b45a7f7d96..2e37819f383 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerPolicy.cs @@ -3,106 +3,107 @@ using Polly.Utilities; using System.Threading; -namespace Polly.CircuitBreaker; - -/// -/// A circuit-breaker policy that can be applied to delegates. -/// -public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy +namespace Polly.CircuitBreaker { - internal readonly ICircuitController _breakerController; - - internal CircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - => _breakerController = breakerController; - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - public Exception LastException => _breakerController.LastException; - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// A circuit-breaker policy that can be applied to delegates. /// - public void Isolate() => _breakerController.Isolate(); + public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy + { + internal readonly ICircuitController _breakerController; + + internal CircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) + => _breakerController = breakerController; + + /// + /// Gets the state of the underlying circuit. + /// + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + public Exception LastException => _breakerController.LastException; + + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + TResult result = default; + CircuitBreakerEngine.Implementation( + (ctx, ct) => { result = action(ctx, ct); return EmptyStruct.Instance; }, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + _breakerController); + return result; + } + } /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// A circuit-breaker policy that can be applied to delegates returning a value of type . /// - public void Reset() => _breakerController.Reset(); - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy { - TResult result = default; - CircuitBreakerEngine.Implementation( - (ctx, ct) => { result = action(ctx, ct); return EmptyStruct.Instance; }, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - _breakerController); - return result; + internal readonly ICircuitController _breakerController; + + internal CircuitBreakerPolicy( + PolicyBuilder policyBuilder, + ICircuitController breakerController + ) : base(policyBuilder) + => _breakerController = breakerController; + + /// + /// Gets the state of the underlying circuit. + /// + public CircuitState CircuitState => _breakerController.CircuitState; + + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was a handled value. + /// + public Exception LastException => _breakerController.LastException; + + /// + /// Gets the last result returned from a user delegate which the circuit-breaker handled. + /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. + /// + public TResult LastHandledResult => _breakerController.LastHandledResult; + + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + public void Isolate() => _breakerController.Isolate(); + + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + public void Reset() => _breakerController.Reset(); + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => CircuitBreakerEngine.Implementation( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _breakerController); } } - -/// -/// A circuit-breaker policy that can be applied to delegates returning a value of type . -/// -public class CircuitBreakerPolicy : Policy, ICircuitBreakerPolicy -{ - internal readonly ICircuitController _breakerController; - - internal CircuitBreakerPolicy( - PolicyBuilder policyBuilder, - ICircuitController breakerController - ) : base(policyBuilder) - => _breakerController = breakerController; - - /// - /// Gets the state of the underlying circuit. - /// - public CircuitState CircuitState => _breakerController.CircuitState; - - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was a handled value. - /// - public Exception LastException => _breakerController.LastException; - - /// - /// Gets the last result returned from a user delegate which the circuit-breaker handled. - /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. - /// - public TResult LastHandledResult => _breakerController.LastHandledResult; - - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - public void Isolate() => _breakerController.Isolate(); - - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - public void Reset() => _breakerController.Reset(); - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => CircuitBreakerEngine.Implementation( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _breakerController); -} \ No newline at end of file diff --git a/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs index c8a5507cb82..a63665a6751 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs @@ -2,216 +2,217 @@ using Polly.CircuitBreaker; using Polly.Utilities; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class CircuitBreakerSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) + public static class CircuitBreakerSyntax { - Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) + { + Action doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.CircuitBreaker - (exceptionsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.CircuitBreaker + (exceptionsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - => policyBuilder.CircuitBreaker( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset() - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + => policyBuilder.CircuitBreaker( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreaker(exceptionsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreaker(exceptionsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, timespan, _) => onBreak(exception, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, timespan, _) => onBreak(exception, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (exception, _, timespan, context) => onBreak(exception, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (exception, _, timespan, context) => onBreak(exception, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions that are handled by this policy are raised consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception, the circuit will break - /// again for another ; if no exception is thrown, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) - { - if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions that are handled by this policy are raised consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception, the circuit will break + /// again for another ; if no exception is thrown, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// exceptionsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset, Action onHalfOpen) + { + if (exceptionsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(exceptionsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - var breakerController = new ConsecutiveCountCircuitController( - exceptionsAllowedBeforeBreaking, - durationOfBreak, - (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); + var breakerController = new ConsecutiveCountCircuitController( + exceptionsAllowedBeforeBreaking, + durationOfBreak, + (outcome, state, timespan, context) => onBreak(outcome.Exception, state, timespan, context), + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } } \ No newline at end of file diff --git a/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs index 0694b5a9f26..5c42d3fbf84 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs @@ -1,216 +1,217 @@ using System; using Polly.CircuitBreaker; -namespace Polly; - -/// -/// Fluent API for defining a Circuit Breaker . -/// -public static class CircuitBreakerTResultSyntax +namespace Polly { /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// + /// Fluent API for defining a Circuit Breaker . /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) + public static class CircuitBreakerTResultSyntax { - Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) + { + Action, TimeSpan> doNothingOnBreak = (_, _) => { }; + var doNothingOnReset = () => { }; - return policyBuilder.CircuitBreaker - (handledEventsAllowedBeforeBreaking, - durationOfBreak, - doNothingOnBreak, - doNothingOnReset - ); - } + return policyBuilder.CircuitBreaker + (handledEventsAllowedBeforeBreaking, + durationOfBreak, + doNothingOnBreak, + doNothingOnReset + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) - => policyBuilder.CircuitBreaker( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset() - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset) + => policyBuilder.CircuitBreaker( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset() + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) - { - var doNothingOnHalfOpen = () => { }; - return policyBuilder.CircuitBreaker(handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - doNothingOnHalfOpen - ); - } + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) + { + var doNothingOnHalfOpen = () => { }; + return policyBuilder.CircuitBreaker(handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + doNothingOnHalfOpen + ); + } - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, timespan, _) => onBreak(outcome, timespan), - _ => onReset(), - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, timespan, _) => onBreak(outcome, timespan), + _ => onReset(), + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - => policyBuilder.CircuitBreaker( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - (outcome, _, timespan, context) => onBreak(outcome, timespan, context), - onReset, - onHalfOpen - ); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + => policyBuilder.CircuitBreaker( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + (outcome, _, timespan, context) => onBreak(outcome, timespan, context), + onReset, + onHalfOpen + ); - /// - /// Builds a that will function like a Circuit Breaker. - /// The circuit will break if - /// exceptions or results that are handled by this policy are encountered consecutively. - /// The circuit will stay broken for the . Any attempt to execute this policy - /// while the circuit is broken, will immediately throw a containing the exception or result - /// that broke the circuit. - /// - /// If the first action after the break duration period results in a handled exception or result, the circuit will break - /// again for another ; if no exception or handled result is encountered, the circuit will reset. - /// - /// - /// The policy builder. - /// The number of exceptions or handled results that are allowed before opening the circuit. - /// The duration the circuit will stay open before resetting. - /// The action to call when the circuit transitions to an state. - /// The action to call when the circuit resets to a state. - /// The action to call when the circuit transitions to state, ready to try action executions again. - /// The policy instance. - /// (see "Release It!" by Michael T. Nygard fi) - /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. - /// onBreak - /// onReset - /// onHalfOpen - public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) - { - if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); - if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); + /// + /// Builds a that will function like a Circuit Breaker. + /// The circuit will break if + /// exceptions or results that are handled by this policy are encountered consecutively. + /// The circuit will stay broken for the . Any attempt to execute this policy + /// while the circuit is broken, will immediately throw a containing the exception or result + /// that broke the circuit. + /// + /// If the first action after the break duration period results in a handled exception or result, the circuit will break + /// again for another ; if no exception or handled result is encountered, the circuit will reset. + /// + /// + /// The policy builder. + /// The number of exceptions or handled results that are allowed before opening the circuit. + /// The duration the circuit will stay open before resetting. + /// The action to call when the circuit transitions to an state. + /// The action to call when the circuit resets to a state. + /// The action to call when the circuit transitions to state, ready to try action executions again. + /// The policy instance. + /// (see "Release It!" by Michael T. Nygard fi) + /// handledEventsAllowedBeforeBreaking;Value must be greater than zero. + /// onBreak + /// onReset + /// onHalfOpen + public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, CircuitState, TimeSpan, Context> onBreak, Action onReset, Action onHalfOpen) + { + if (handledEventsAllowedBeforeBreaking <= 0) throw new ArgumentOutOfRangeException(nameof(handledEventsAllowedBeforeBreaking), "Value must be greater than zero."); + if (durationOfBreak < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(durationOfBreak), "Value must be greater than zero."); - if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); - if (onReset == null) throw new ArgumentNullException(nameof(onReset)); - if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); + if (onBreak == null) throw new ArgumentNullException(nameof(onBreak)); + if (onReset == null) throw new ArgumentNullException(nameof(onReset)); + if (onHalfOpen == null) throw new ArgumentNullException(nameof(onHalfOpen)); - ICircuitController breakerController = new ConsecutiveCountCircuitController( - handledEventsAllowedBeforeBreaking, - durationOfBreak, - onBreak, - onReset, - onHalfOpen); - return new CircuitBreakerPolicy( - policyBuilder, - breakerController - ); + ICircuitController breakerController = new ConsecutiveCountCircuitController( + handledEventsAllowedBeforeBreaking, + durationOfBreak, + onBreak, + onReset, + onHalfOpen); + return new CircuitBreakerPolicy( + policyBuilder, + breakerController + ); + } } } \ No newline at end of file diff --git a/src/Polly/CircuitBreaker/CircuitState.cs b/src/Polly/CircuitBreaker/CircuitState.cs index c75762dfa9c..c02151400e6 100644 --- a/src/Polly/CircuitBreaker/CircuitState.cs +++ b/src/Polly/CircuitBreaker/CircuitState.cs @@ -1,24 +1,25 @@ -namespace Polly.CircuitBreaker; - -/// -/// Describes the possible states the circuit of a CircuitBreaker may be in. -/// -public enum CircuitState +namespace Polly.CircuitBreaker { /// - /// Closed - When the circuit is closed. Execution of actions is allowed. + /// Describes the possible states the circuit of a CircuitBreaker may be in. /// - Closed, - /// - /// Open - When the automated controller has opened the circuit (typically due to some failure threshold being exceeded by recent actions). Execution of actions is blocked. - /// - Open, - /// - /// Half-open - When the circuit is half-open, it is recovering from an open state. The duration of break of the preceding open state has typically passed. In the half-open state, actions may be executed, but the results of these actions may be treated with criteria different to normal operation, to decide if the circuit has recovered sufficiently to be placed back in to the closed state, or if continuing failures mean the circuit should revert to open perhaps more quickly than in normal operation. - /// - HalfOpen, - /// - /// Isolated - When the circuit has been placed into a fixed open state by a call to . This isolates the circuit manually, blocking execution of all actions until a call to is made. - /// - Isolated -} \ No newline at end of file + public enum CircuitState + { + /// + /// Closed - When the circuit is closed. Execution of actions is allowed. + /// + Closed, + /// + /// Open - When the automated controller has opened the circuit (typically due to some failure threshold being exceeded by recent actions). Execution of actions is blocked. + /// + Open, + /// + /// Half-open - When the circuit is half-open, it is recovering from an open state. The duration of break of the preceding open state has typically passed. In the half-open state, actions may be executed, but the results of these actions may be treated with criteria different to normal operation, to decide if the circuit has recovered sufficiently to be placed back in to the closed state, or if continuing failures mean the circuit should revert to open perhaps more quickly than in normal operation. + /// + HalfOpen, + /// + /// Isolated - When the circuit has been placed into a fixed open state by a call to . This isolates the circuit manually, blocking execution of all actions until a call to is made. + /// + Isolated + } +} diff --git a/src/Polly/CircuitBreaker/CircuitStateController.cs b/src/Polly/CircuitBreaker/CircuitStateController.cs index 2c32488ad1a..e44737312f4 100644 --- a/src/Polly/CircuitBreaker/CircuitStateController.cs +++ b/src/Polly/CircuitBreaker/CircuitStateController.cs @@ -2,179 +2,181 @@ using System.Threading; using Polly.Utilities; -namespace Polly.CircuitBreaker; - -internal abstract class CircuitStateController : ICircuitController +namespace Polly.CircuitBreaker { - protected readonly TimeSpan _durationOfBreak; - protected long _blockedTill; - protected CircuitState _circuitState; - protected DelegateResult _lastOutcome; - - protected readonly Action, CircuitState, TimeSpan, Context> _onBreak; - protected readonly Action _onReset; - protected readonly Action _onHalfOpen; - - protected readonly object _lock = new object(); - - protected CircuitStateController( - TimeSpan durationOfBreak, - Action, CircuitState, TimeSpan, Context> onBreak, - Action onReset, - Action onHalfOpen) + internal abstract class CircuitStateController : ICircuitController { - _durationOfBreak = durationOfBreak; - _onBreak = onBreak; - _onReset = onReset; - _onHalfOpen = onHalfOpen; + protected readonly TimeSpan _durationOfBreak; + protected long _blockedTill; + protected CircuitState _circuitState; + protected DelegateResult _lastOutcome; + + protected readonly Action, CircuitState, TimeSpan, Context> _onBreak; + protected readonly Action _onReset; + protected readonly Action _onHalfOpen; + + protected readonly object _lock = new object(); + + protected CircuitStateController( + TimeSpan durationOfBreak, + Action, CircuitState, TimeSpan, Context> onBreak, + Action onReset, + Action onHalfOpen) + { + _durationOfBreak = durationOfBreak; + _onBreak = onBreak; + _onReset = onReset; + _onHalfOpen = onHalfOpen; - _circuitState = CircuitState.Closed; - Reset(); - } + _circuitState = CircuitState.Closed; + Reset(); + } - public CircuitState CircuitState - { - get + public CircuitState CircuitState { - if (_circuitState != CircuitState.Open) + get { - return _circuitState; - } + if (_circuitState != CircuitState.Open) + { + return _circuitState; + } - using (TimedLock.Lock(_lock)) - { - if (_circuitState == CircuitState.Open && !IsInAutomatedBreak_NeedsLock) + using (TimedLock.Lock(_lock)) { - _circuitState = CircuitState.HalfOpen; - _onHalfOpen(); + if (_circuitState == CircuitState.Open && !IsInAutomatedBreak_NeedsLock) + { + _circuitState = CircuitState.HalfOpen; + _onHalfOpen(); + } + return _circuitState; } - return _circuitState; } } - } - public Exception LastException - { - get + public Exception LastException { - using (TimedLock.Lock(_lock)) + get { - return _lastOutcome?.Exception; + using (TimedLock.Lock(_lock)) + { + return _lastOutcome?.Exception; + } } } - } - public TResult LastHandledResult - { - get + public TResult LastHandledResult { - using (TimedLock.Lock(_lock)) + get { - return _lastOutcome != null - ? _lastOutcome.Result : default; + using (TimedLock.Lock(_lock)) + { + return _lastOutcome != null + ? _lastOutcome.Result : default; + } } } - } - protected bool IsInAutomatedBreak_NeedsLock - { - get + protected bool IsInAutomatedBreak_NeedsLock { - return SystemClock.UtcNow().Ticks < _blockedTill; + get + { + return SystemClock.UtcNow().Ticks < _blockedTill; + } } - } - public void Isolate() - { - using (TimedLock.Lock(_lock)) + public void Isolate() { - _lastOutcome = new DelegateResult(new IsolatedCircuitException("The circuit is manually held open and is not allowing calls.")); - BreakFor_NeedsLock(TimeSpan.MaxValue, Context.None()); - _circuitState = CircuitState.Isolated; + using (TimedLock.Lock(_lock)) + { + _lastOutcome = new DelegateResult(new IsolatedCircuitException("The circuit is manually held open and is not allowing calls.")); + BreakFor_NeedsLock(TimeSpan.MaxValue, Context.None()); + _circuitState = CircuitState.Isolated; + } } - } - - protected void Break_NeedsLock(Context context) => BreakFor_NeedsLock(_durationOfBreak, context); - private void BreakFor_NeedsLock(TimeSpan durationOfBreak, Context context) - { - var willDurationTakeUsPastDateTimeMaxValue = durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow(); - _blockedTill = willDurationTakeUsPastDateTimeMaxValue - ? DateTime.MaxValue.Ticks - : (SystemClock.UtcNow() + durationOfBreak).Ticks; + protected void Break_NeedsLock(Context context) => BreakFor_NeedsLock(_durationOfBreak, context); - var transitionedState = _circuitState; - _circuitState = CircuitState.Open; + private void BreakFor_NeedsLock(TimeSpan durationOfBreak, Context context) + { + var willDurationTakeUsPastDateTimeMaxValue = durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow(); + _blockedTill = willDurationTakeUsPastDateTimeMaxValue + ? DateTime.MaxValue.Ticks + : (SystemClock.UtcNow() + durationOfBreak).Ticks; - _onBreak(_lastOutcome, transitionedState, durationOfBreak, context); - } + var transitionedState = _circuitState; + _circuitState = CircuitState.Open; - public void Reset() => OnCircuitReset(Context.None()); + _onBreak(_lastOutcome, transitionedState, durationOfBreak, context); + } - protected void ResetInternal_NeedsLock(Context context) - { - _blockedTill = DateTime.MinValue.Ticks; - _lastOutcome = null; + public void Reset() => OnCircuitReset(Context.None()); - var priorState = _circuitState; - _circuitState = CircuitState.Closed; - if (priorState != CircuitState.Closed) + protected void ResetInternal_NeedsLock(Context context) { - _onReset(context); - } - } + _blockedTill = DateTime.MinValue.Ticks; + _lastOutcome = null; - protected bool PermitHalfOpenCircuitTest() - { - var currentlyBlockedUntil = _blockedTill; - if (SystemClock.UtcNow().Ticks >= currentlyBlockedUntil) - { - // It's time to permit a / another trial call in the half-open state ... - // ... but to prevent race conditions/multiple calls, we have to ensure only _one_ thread wins the race to own this next call. - return Interlocked.CompareExchange(ref _blockedTill, SystemClock.UtcNow().Ticks + _durationOfBreak.Ticks, currentlyBlockedUntil) == currentlyBlockedUntil; + var priorState = _circuitState; + _circuitState = CircuitState.Closed; + if (priorState != CircuitState.Closed) + { + _onReset(context); + } } - return false; - } - private BrokenCircuitException GetBreakingException() - { - const string BrokenCircuitMessage = "The circuit is now open and is not allowing calls."; - - var lastOutcome = _lastOutcome; - if (lastOutcome == null) + protected bool PermitHalfOpenCircuitTest() { - return new BrokenCircuitException(BrokenCircuitMessage); + var currentlyBlockedUntil = _blockedTill; + if (SystemClock.UtcNow().Ticks >= currentlyBlockedUntil) + { + // It's time to permit a / another trial call in the half-open state ... + // ... but to prevent race conditions/multiple calls, we have to ensure only _one_ thread wins the race to own this next call. + return Interlocked.CompareExchange(ref _blockedTill, SystemClock.UtcNow().Ticks + _durationOfBreak.Ticks, currentlyBlockedUntil) == currentlyBlockedUntil; + } + return false; } - if (lastOutcome.Exception != null) + private BrokenCircuitException GetBreakingException() { - return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Exception); - } + const string BrokenCircuitMessage = "The circuit is now open and is not allowing calls."; - return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Result); - } + var lastOutcome = _lastOutcome; + if (lastOutcome == null) + { + return new BrokenCircuitException(BrokenCircuitMessage); + } - public void OnActionPreExecute() - { - switch (CircuitState) + if (lastOutcome.Exception != null) + { + return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Exception); + } + + return new BrokenCircuitException(BrokenCircuitMessage, lastOutcome.Result); + } + + public void OnActionPreExecute() { - case CircuitState.Closed: - break; - case CircuitState.HalfOpen: - if (!PermitHalfOpenCircuitTest()) { throw GetBreakingException(); } - break; - case CircuitState.Open: - throw GetBreakingException(); - case CircuitState.Isolated: - throw new IsolatedCircuitException("The circuit is manually held open and is not allowing calls."); - default: - throw new InvalidOperationException("Unhandled CircuitState."); + switch (CircuitState) + { + case CircuitState.Closed: + break; + case CircuitState.HalfOpen: + if (!PermitHalfOpenCircuitTest()) { throw GetBreakingException(); } + break; + case CircuitState.Open: + throw GetBreakingException(); + case CircuitState.Isolated: + throw new IsolatedCircuitException("The circuit is manually held open and is not allowing calls."); + default: + throw new InvalidOperationException("Unhandled CircuitState."); + } } - } - public abstract void OnActionSuccess(Context context); + public abstract void OnActionSuccess(Context context); + + public abstract void OnActionFailure(DelegateResult outcome, Context context); - public abstract void OnActionFailure(DelegateResult outcome, Context context); + public abstract void OnCircuitReset(Context context); + } +} - public abstract void OnCircuitReset(Context context); -} \ No newline at end of file diff --git a/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs b/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs index f1c8b9dd15d..78a504dd92c 100644 --- a/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs +++ b/src/Polly/CircuitBreaker/ConsecutiveCountCircuitController.cs @@ -1,85 +1,86 @@ using System; using Polly.Utilities; -namespace Polly.CircuitBreaker; - -internal class ConsecutiveCountCircuitController : CircuitStateController +namespace Polly.CircuitBreaker { - private readonly int _exceptionsAllowedBeforeBreaking; - private int _consecutiveFailureCount; - - public ConsecutiveCountCircuitController( - int exceptionsAllowedBeforeBreaking, - TimeSpan durationOfBreak, - Action, CircuitState, TimeSpan, Context> onBreak, - Action onReset, - Action onHalfOpen - ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) + internal class ConsecutiveCountCircuitController : CircuitStateController { - _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - } + private readonly int _exceptionsAllowedBeforeBreaking; + private int _consecutiveFailureCount; - public override void OnCircuitReset(Context context) - { - using (TimedLock.Lock(_lock)) + public ConsecutiveCountCircuitController( + int exceptionsAllowedBeforeBreaking, + TimeSpan durationOfBreak, + Action, CircuitState, TimeSpan, Context> onBreak, + Action onReset, + Action onHalfOpen + ) : base(durationOfBreak, onBreak, onReset, onHalfOpen) { - _consecutiveFailureCount = 0; + _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + } + + public override void OnCircuitReset(Context context) + { + using (TimedLock.Lock(_lock)) + { + _consecutiveFailureCount = 0; - ResetInternal_NeedsLock(context); + ResetInternal_NeedsLock(context); + } } - } - public override void OnActionSuccess(Context context) - { - using (TimedLock.Lock(_lock)) + public override void OnActionSuccess(Context context) { - switch (_circuitState) + using (TimedLock.Lock(_lock)) { - case CircuitState.HalfOpen: - OnCircuitReset(context); - break; + switch (_circuitState) + { + case CircuitState.HalfOpen: + OnCircuitReset(context); + break; - case CircuitState.Closed: - _consecutiveFailureCount = 0; - break; + case CircuitState.Closed: + _consecutiveFailureCount = 0; + break; - case CircuitState.Open: - case CircuitState.Isolated: - break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; only time passing governs transitioning from Open to HalfOpen state. + case CircuitState.Open: + case CircuitState.Isolated: + break; // A successful call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; only time passing governs transitioning from Open to HalfOpen state. - default: - throw new InvalidOperationException("Unhandled CircuitState."); + default: + throw new InvalidOperationException("Unhandled CircuitState."); + } } } - } - public override void OnActionFailure(DelegateResult outcome, Context context) - { - using (TimedLock.Lock(_lock)) + public override void OnActionFailure(DelegateResult outcome, Context context) { - _lastOutcome = outcome; - - switch (_circuitState) + using (TimedLock.Lock(_lock)) { - case CircuitState.HalfOpen: - Break_NeedsLock(context); - return; + _lastOutcome = outcome; - case CircuitState.Closed: - _consecutiveFailureCount += 1; - if (_consecutiveFailureCount >= _exceptionsAllowedBeforeBreaking) - { + switch (_circuitState) + { + case CircuitState.HalfOpen: Break_NeedsLock(context); - } - break; + return; + + case CircuitState.Closed: + _consecutiveFailureCount += 1; + if (_consecutiveFailureCount >= _exceptionsAllowedBeforeBreaking) + { + Break_NeedsLock(context); + } + break; - case CircuitState.Open: - case CircuitState.Isolated: - break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). + case CircuitState.Open: + case CircuitState.Isolated: + break; // A failure call result may arrive when the circuit is open, if it was placed before the circuit broke. We take no action; we do not want to duplicate-signal onBreak; we do not want to extend time for which the circuit is broken. We do not want to mask the fact that the call executed (as replacing its result with a Broken/IsolatedCircuitException would do). - default: - throw new InvalidOperationException("Unhandled CircuitState."); + default: + throw new InvalidOperationException("Unhandled CircuitState."); + } } } } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/HealthCount.cs b/src/Polly/CircuitBreaker/HealthCount.cs index 887b53b30fa..e5112ca4a1b 100644 --- a/src/Polly/CircuitBreaker/HealthCount.cs +++ b/src/Polly/CircuitBreaker/HealthCount.cs @@ -1,12 +1,13 @@ -namespace Polly.CircuitBreaker; - -internal class HealthCount +namespace Polly.CircuitBreaker { - public int Successes { get; set; } + internal class HealthCount + { + public int Successes { get; set; } - public int Failures { get; set; } + public int Failures { get; set; } - public int Total { get { return Successes + Failures; } } + public int Total { get { return Successes + Failures; } } - public long StartedAt { get; set; } -} \ No newline at end of file + public long StartedAt { get; set; } + } +} diff --git a/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs b/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs index 6532faf1cc5..b19922e6779 100644 --- a/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs +++ b/src/Polly/CircuitBreaker/ICircuitBreakerPolicy.cs @@ -1,42 +1,43 @@ using System; -namespace Polly.CircuitBreaker; - -/// -/// Defines properties and methods common to all circuit-breaker policies. -/// -public interface ICircuitBreakerPolicy : IsPolicy +namespace Polly.CircuitBreaker { /// - /// Gets the state of the underlying circuit. + /// Defines properties and methods common to all circuit-breaker policies. /// - CircuitState CircuitState { get; } + public interface ICircuitBreakerPolicy : IsPolicy + { + /// + /// Gets the state of the underlying circuit. + /// + CircuitState CircuitState { get; } - /// - /// Gets the last exception handled by the circuit-breaker. - /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. - /// - Exception LastException { get; } + /// + /// Gets the last exception handled by the circuit-breaker. + /// This will be null if no exceptions have been handled by the circuit-breaker since the circuit last closed. + /// + Exception LastException { get; } - /// - /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. - /// - void Isolate(); + /// + /// Isolates (opens) the circuit manually, and holds it in this state until a call to is made. + /// + void Isolate(); - /// - /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. - /// - void Reset(); -} + /// + /// Closes the circuit, and resets any statistics controlling automated circuit-breaking. + /// + void Reset(); + } -/// -/// Defines properties and methods common to all circuit-breaker policies generic-typed for executions returning results of type . -/// -public interface ICircuitBreakerPolicy : ICircuitBreakerPolicy -{ /// - /// Gets the last result returned from a user delegate which the circuit-breaker handled. - /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. + /// Defines properties and methods common to all circuit-breaker policies generic-typed for executions returning results of type . /// - TResult LastHandledResult { get; } -} \ No newline at end of file + public interface ICircuitBreakerPolicy : ICircuitBreakerPolicy + { + /// + /// Gets the last result returned from a user delegate which the circuit-breaker handled. + /// This will be default() if no results have been handled by the circuit-breaker since the circuit last closed, or if the last event handled by the circuit was an exception. + /// + TResult LastHandledResult { get; } + } +} diff --git a/src/Polly/CircuitBreaker/ICircuitController.cs b/src/Polly/CircuitBreaker/ICircuitController.cs index 494611e6c82..a24e1364593 100644 --- a/src/Polly/CircuitBreaker/ICircuitController.cs +++ b/src/Polly/CircuitBreaker/ICircuitController.cs @@ -1,16 +1,17 @@ using System; -namespace Polly.CircuitBreaker; - -internal interface ICircuitController +namespace Polly.CircuitBreaker { - CircuitState CircuitState { get; } - Exception LastException { get; } - TResult LastHandledResult { get; } - void Isolate(); - void Reset(); - void OnCircuitReset(Context context); - void OnActionPreExecute(); - void OnActionSuccess(Context context); - void OnActionFailure(DelegateResult outcome, Context context); -} \ No newline at end of file + internal interface ICircuitController + { + CircuitState CircuitState { get; } + Exception LastException { get; } + TResult LastHandledResult { get; } + void Isolate(); + void Reset(); + void OnCircuitReset(Context context); + void OnActionPreExecute(); + void OnActionSuccess(Context context); + void OnActionFailure(DelegateResult outcome, Context context); + } +} diff --git a/src/Polly/CircuitBreaker/IHealthMetrics.cs b/src/Polly/CircuitBreaker/IHealthMetrics.cs index 14f1495ced1..bb131059642 100644 --- a/src/Polly/CircuitBreaker/IHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/IHealthMetrics.cs @@ -1,11 +1,12 @@ -namespace Polly.CircuitBreaker; - -internal interface IHealthMetrics +namespace Polly.CircuitBreaker { - void IncrementSuccess_NeedsLock(); - void IncrementFailure_NeedsLock(); + internal interface IHealthMetrics + { + void IncrementSuccess_NeedsLock(); + void IncrementFailure_NeedsLock(); - void Reset_NeedsLock(); + void Reset_NeedsLock(); - HealthCount GetHealthCount_NeedsLock(); -} \ No newline at end of file + HealthCount GetHealthCount_NeedsLock(); + } +} diff --git a/src/Polly/CircuitBreaker/RollingHealthMetrics.cs b/src/Polly/CircuitBreaker/RollingHealthMetrics.cs index 1efb629e91d..44e4fb25fc1 100644 --- a/src/Polly/CircuitBreaker/RollingHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/RollingHealthMetrics.cs @@ -2,74 +2,75 @@ using System.Collections.Generic; using Polly.Utilities; -namespace Polly.CircuitBreaker; - -internal class RollingHealthMetrics : IHealthMetrics +namespace Polly.CircuitBreaker { - private readonly long _samplingDuration; - private readonly long _windowDuration; - private readonly Queue _windows; - - private HealthCount _currentWindow; - - public RollingHealthMetrics(TimeSpan samplingDuration, short numberOfWindows) + internal class RollingHealthMetrics : IHealthMetrics { - _samplingDuration = samplingDuration.Ticks; + private readonly long _samplingDuration; + private readonly long _windowDuration; + private readonly Queue _windows; - _windowDuration = _samplingDuration / numberOfWindows; - _windows = new Queue(numberOfWindows + 1); - } + private HealthCount _currentWindow; - public void IncrementSuccess_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public RollingHealthMetrics(TimeSpan samplingDuration, short numberOfWindows) + { + _samplingDuration = samplingDuration.Ticks; - _currentWindow.Successes++; - } + _windowDuration = _samplingDuration / numberOfWindows; + _windows = new Queue(numberOfWindows + 1); + } - public void IncrementFailure_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public void IncrementSuccess_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - _currentWindow.Failures++; - } + _currentWindow.Successes++; + } - public void Reset_NeedsLock() - { - _currentWindow = null; - _windows.Clear(); - } + public void IncrementFailure_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - public HealthCount GetHealthCount_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + _currentWindow.Failures++; + } - var successes = 0; - var failures = 0; - foreach (var window in _windows) + public void Reset_NeedsLock() { - successes += window.Successes; - failures += window.Failures; + _currentWindow = null; + _windows.Clear(); } - return new HealthCount + public HealthCount GetHealthCount_NeedsLock() { - Successes = successes, - Failures = failures, - StartedAt = _windows.Peek().StartedAt - }; - } + ActualiseCurrentMetric_NeedsLock(); - private void ActualiseCurrentMetric_NeedsLock() - { - var now = SystemClock.UtcNow().Ticks; - if (_currentWindow == null || now - _currentWindow.StartedAt >= _windowDuration) - { - _currentWindow = new HealthCount { StartedAt = now }; - _windows.Enqueue(_currentWindow); + var successes = 0; + var failures = 0; + foreach (var window in _windows) + { + successes += window.Successes; + failures += window.Failures; + } + + return new HealthCount + { + Successes = successes, + Failures = failures, + StartedAt = _windows.Peek().StartedAt + }; } - while (_windows.Count > 0 && (now - _windows.Peek().StartedAt >= _samplingDuration)) - _windows.Dequeue(); + private void ActualiseCurrentMetric_NeedsLock() + { + var now = SystemClock.UtcNow().Ticks; + if (_currentWindow == null || now - _currentWindow.StartedAt >= _windowDuration) + { + _currentWindow = new HealthCount { StartedAt = now }; + _windows.Enqueue(_currentWindow); + } + + while (_windows.Count > 0 && (now - _windows.Peek().StartedAt >= _samplingDuration)) + _windows.Dequeue(); + } } -} \ No newline at end of file +} diff --git a/src/Polly/CircuitBreaker/SingleHealthMetrics.cs b/src/Polly/CircuitBreaker/SingleHealthMetrics.cs index 2bae9c9927d..f56546720b6 100644 --- a/src/Polly/CircuitBreaker/SingleHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/SingleHealthMetrics.cs @@ -1,45 +1,46 @@ using System; using Polly.Utilities; -namespace Polly.CircuitBreaker; - -internal class SingleHealthMetrics : IHealthMetrics +namespace Polly.CircuitBreaker { - private readonly long _samplingDuration; + internal class SingleHealthMetrics : IHealthMetrics + { + private readonly long _samplingDuration; - private HealthCount _current; + private HealthCount _current; - public SingleHealthMetrics(TimeSpan samplingDuration) => _samplingDuration = samplingDuration.Ticks; + public SingleHealthMetrics(TimeSpan samplingDuration) => _samplingDuration = samplingDuration.Ticks; - public void IncrementSuccess_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public void IncrementSuccess_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - _current.Successes++; - } + _current.Successes++; + } - public void IncrementFailure_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public void IncrementFailure_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - _current.Failures++; - } + _current.Failures++; + } - public void Reset_NeedsLock() => _current = null; + public void Reset_NeedsLock() => _current = null; - public HealthCount GetHealthCount_NeedsLock() - { - ActualiseCurrentMetric_NeedsLock(); + public HealthCount GetHealthCount_NeedsLock() + { + ActualiseCurrentMetric_NeedsLock(); - return _current; - } + return _current; + } - private void ActualiseCurrentMetric_NeedsLock() - { - var now = SystemClock.UtcNow().Ticks; - if (_current == null || now - _current.StartedAt >= _samplingDuration) + private void ActualiseCurrentMetric_NeedsLock() { - _current = new HealthCount { StartedAt = now }; + var now = SystemClock.UtcNow().Ticks; + if (_current == null || now - _current.StartedAt >= _samplingDuration) + { + _current = new HealthCount { StartedAt = now }; + } } } -} \ No newline at end of file +} diff --git a/src/Polly/Context.Dictionary.cs b/src/Polly/Context.Dictionary.cs index fae4cf1c076..12ed0043927 100644 --- a/src/Polly/Context.Dictionary.cs +++ b/src/Polly/Context.Dictionary.cs @@ -2,137 +2,138 @@ using System.Collections; using System.Collections.Generic; -namespace Polly; - -/// -/// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. -/// Do not re-use an instance of across more than one execution. -/// -public partial class Context : IDictionary, IDictionary, IReadOnlyDictionary +namespace Polly { - // For an individual execution through a policy or policywrap, it is expected that all execution steps (for example executing the user delegate, invoking policy-activity delegates such as onRetry, onBreak, onTimeout etc) execute sequentially. - // Therefore, this class is intentionally not constructed to be safe for concurrent access from multiple threads. - - private Dictionary wrappedDictionary; - - private Dictionary WrappedDictionary => wrappedDictionary ?? (wrappedDictionary = new Dictionary()); - /// - /// Initializes a new instance of the class, with the specified and the supplied . + /// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. + /// Do not re-use an instance of across more than one execution. /// - /// The operation key. - /// The context data. - public Context(string operationKey, IDictionary contextData) : this(contextData) + public partial class Context : IDictionary, IDictionary, IReadOnlyDictionary { - OperationKey = operationKey; - } + // For an individual execution through a policy or policywrap, it is expected that all execution steps (for example executing the user delegate, invoking policy-activity delegates such as onRetry, onBreak, onTimeout etc) execute sequentially. + // Therefore, this class is intentionally not constructed to be safe for concurrent access from multiple threads. - internal Context(IDictionary contextData) : this() - { - if (contextData == null) throw new ArgumentNullException(nameof(contextData)); - wrappedDictionary = new Dictionary(contextData); - } + private Dictionary wrappedDictionary; - #region IDictionary implementation + private Dictionary WrappedDictionary => wrappedDictionary ?? (wrappedDictionary = new Dictionary()); - /// - public ICollection Keys => WrappedDictionary.Keys; + /// + /// Initializes a new instance of the class, with the specified and the supplied . + /// + /// The operation key. + /// The context data. + public Context(string operationKey, IDictionary contextData) : this(contextData) + { + OperationKey = operationKey; + } - /// - public ICollection Values => WrappedDictionary.Values; + internal Context(IDictionary contextData) : this() + { + if (contextData == null) throw new ArgumentNullException(nameof(contextData)); + wrappedDictionary = new Dictionary(contextData); + } - /// - public int Count => WrappedDictionary.Count; +#region IDictionary implementation - /// - bool ICollection>.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; + /// + public ICollection Keys => WrappedDictionary.Keys; - /// - public object this[string key] - { - get => WrappedDictionary[key]; - set => WrappedDictionary[key] = value; - } + /// + public ICollection Values => WrappedDictionary.Values; - /// - public void Add(string key, object value) - { - WrappedDictionary.Add(key, value); - } + /// + public int Count => WrappedDictionary.Count; + + /// + bool ICollection>.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; - /// - public bool ContainsKey(string key) => WrappedDictionary.ContainsKey(key); + /// + public object this[string key] + { + get => WrappedDictionary[key]; + set => WrappedDictionary[key] = value; + } - /// - public bool Remove(string key) => WrappedDictionary.Remove(key); + /// + public void Add(string key, object value) + { + WrappedDictionary.Add(key, value); + } - /// - public bool TryGetValue(string key, out object value) => WrappedDictionary.TryGetValue(key, out value); + /// + public bool ContainsKey(string key) => WrappedDictionary.ContainsKey(key); - /// - void ICollection>.Add(KeyValuePair item) => ((IDictionary)WrappedDictionary).Add(item); + /// + public bool Remove(string key) => WrappedDictionary.Remove(key); - /// - public void Clear() => WrappedDictionary.Clear(); + /// + public bool TryGetValue(string key, out object value) => WrappedDictionary.TryGetValue(key, out value); - /// - bool ICollection>.Contains(KeyValuePair item) => ((IDictionary)WrappedDictionary).Contains(item); + /// + void ICollection>.Add(KeyValuePair item) => ((IDictionary)WrappedDictionary).Add(item); - /// - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((IDictionary) WrappedDictionary).CopyTo(array, arrayIndex); + /// + public void Clear() => WrappedDictionary.Clear(); - /// - bool ICollection>.Remove(KeyValuePair item) => ((IDictionary)WrappedDictionary).Remove(item); + /// + bool ICollection>.Contains(KeyValuePair item) => ((IDictionary)WrappedDictionary).Contains(item); - /// - public IEnumerator> GetEnumerator() => WrappedDictionary.GetEnumerator(); + /// + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((IDictionary) WrappedDictionary).CopyTo(array, arrayIndex); - /// - IEnumerator IEnumerable.GetEnumerator() => WrappedDictionary.GetEnumerator(); + /// + bool ICollection>.Remove(KeyValuePair item) => ((IDictionary)WrappedDictionary).Remove(item); - /// - public void Add(object key, object value) => ((IDictionary)WrappedDictionary).Add(key, value); + /// + public IEnumerator> GetEnumerator() => WrappedDictionary.GetEnumerator(); - /// - public bool Contains(object key) => ((IDictionary)WrappedDictionary).Contains(key); + /// + IEnumerator IEnumerable.GetEnumerator() => WrappedDictionary.GetEnumerator(); - /// - IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)WrappedDictionary).GetEnumerator(); + /// + public void Add(object key, object value) => ((IDictionary)WrappedDictionary).Add(key, value); - /// - public void Remove(object key) => ((IDictionary)WrappedDictionary).Remove(key); + /// + public bool Contains(object key) => ((IDictionary)WrappedDictionary).Contains(key); - /// - public void CopyTo(Array array, int index) => ((IDictionary)WrappedDictionary).CopyTo(array, index); + /// + IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)WrappedDictionary).GetEnumerator(); - #endregion + /// + public void Remove(object key) => ((IDictionary)WrappedDictionary).Remove(key); - #region IReadOnlyDictionary implementation - IEnumerable IReadOnlyDictionary.Keys => ((IReadOnlyDictionary)WrappedDictionary).Keys; + /// + public void CopyTo(Array array, int index) => ((IDictionary)WrappedDictionary).CopyTo(array, index); - IEnumerable IReadOnlyDictionary.Values => ((IReadOnlyDictionary)WrappedDictionary).Values; - #endregion + #endregion - #region IDictionary implementation + #region IReadOnlyDictionary implementation + IEnumerable IReadOnlyDictionary.Keys => ((IReadOnlyDictionary)WrappedDictionary).Keys; - /// - bool IDictionary.IsFixedSize => ((IDictionary)WrappedDictionary).IsFixedSize; + IEnumerable IReadOnlyDictionary.Values => ((IReadOnlyDictionary)WrappedDictionary).Values; + #endregion - /// - bool IDictionary.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; + #region IDictionary implementation - ICollection IDictionary.Keys => ((IDictionary)WrappedDictionary).Keys; + /// + bool IDictionary.IsFixedSize => ((IDictionary)WrappedDictionary).IsFixedSize; - ICollection IDictionary.Values => ((IDictionary)WrappedDictionary).Values; + /// + bool IDictionary.IsReadOnly => ((IDictionary)WrappedDictionary).IsReadOnly; - /// - bool ICollection.IsSynchronized => ((IDictionary)WrappedDictionary).IsSynchronized; + ICollection IDictionary.Keys => ((IDictionary)WrappedDictionary).Keys; - /// - object ICollection.SyncRoot => ((IDictionary)WrappedDictionary).SyncRoot; + ICollection IDictionary.Values => ((IDictionary)WrappedDictionary).Values; - /// - object IDictionary.this[object key] { get => ((IDictionary)WrappedDictionary)[key]; set => ((IDictionary)WrappedDictionary)[key] = value; } + /// + bool ICollection.IsSynchronized => ((IDictionary)WrappedDictionary).IsSynchronized; - #endregion + /// + object ICollection.SyncRoot => ((IDictionary)WrappedDictionary).SyncRoot; + + /// + object IDictionary.this[object key] { get => ((IDictionary)WrappedDictionary)[key]; set => ((IDictionary)WrappedDictionary)[key] = value; } + +#endregion + } } \ No newline at end of file diff --git a/src/Polly/Context.cs b/src/Polly/Context.cs index 107e00ce2b5..65d0949eca1 100644 --- a/src/Polly/Context.cs +++ b/src/Polly/Context.cs @@ -1,58 +1,59 @@ using System; using Polly.Wrap; -namespace Polly; - -/// -/// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. -/// Do not re-use an instance of across more than one call through .Execute(...) or .ExecuteAsync(...). -/// -public partial class Context +namespace Polly { - internal static Context None() => new Context(); - - private Guid? _correlationId; - /// - /// Initializes a new instance of the class, with the specified . + /// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added. + /// Do not re-use an instance of across more than one call through .Execute(...) or .ExecuteAsync(...). /// - /// The operation key. - public Context(string operationKey) => OperationKey = operationKey; - - /// - /// Initializes a new instance of the class. - /// - public Context() + public partial class Context { - } + internal static Context None() => new Context(); - /// - /// When execution is through a , identifies the PolicyWrap executing the current delegate by returning the of the outermost layer in the PolicyWrap; otherwise, null. - /// - public string PolicyWrapKey { get; internal set; } + private Guid? _correlationId; - /// - /// The of the policy instance executing the current delegate. - /// - public string PolicyKey { get; internal set; } + /// + /// Initializes a new instance of the class, with the specified . + /// + /// The operation key. + public Context(string operationKey) => OperationKey = operationKey; - /// - /// A key unique to the call site of the current execution. - /// Policy instances are commonly reused across multiple call sites. Set an OperationKey so that logging and metrics can distinguish usages of policy instances at different call sites. - /// The value is set by using the constructor taking an operationKey parameter. - /// - public string OperationKey { get; } + /// + /// Initializes a new instance of the class. + /// + public Context() + { + } - /// - /// A Guid guaranteed to be unique to each execution. - /// Acts as a correlation id so that events specific to a single execution can be identified in logging and telemetry. - /// - public Guid CorrelationId - { - get + /// + /// When execution is through a , identifies the PolicyWrap executing the current delegate by returning the of the outermost layer in the PolicyWrap; otherwise, null. + /// + public string PolicyWrapKey { get; internal set; } + + /// + /// The of the policy instance executing the current delegate. + /// + public string PolicyKey { get; internal set; } + + /// + /// A key unique to the call site of the current execution. + /// Policy instances are commonly reused across multiple call sites. Set an OperationKey so that logging and metrics can distinguish usages of policy instances at different call sites. + /// The value is set by using the constructor taking an operationKey parameter. + /// + public string OperationKey { get; } + + /// + /// A Guid guaranteed to be unique to each execution. + /// Acts as a correlation id so that events specific to a single execution can be identified in logging and telemetry. + /// + public Guid CorrelationId { - if (!_correlationId.HasValue) { _correlationId = Guid.NewGuid(); } - return _correlationId.Value; + get + { + if (!_correlationId.HasValue) { _correlationId = Guid.NewGuid(); } + return _correlationId.Value; + } } } -} \ No newline at end of file +} diff --git a/src/Polly/DelegateResult.cs b/src/Polly/DelegateResult.cs index 9bc11cce986..5cdee1de360 100644 --- a/src/Polly/DelegateResult.cs +++ b/src/Polly/DelegateResult.cs @@ -1,31 +1,32 @@ using System; -namespace Polly; - -/// -/// The captured outcome of executing an individual Func<TResult> -/// -public class DelegateResult +namespace Polly { /// - /// Create an instance of representing an execution which returned + /// The captured outcome of executing an individual Func<TResult> /// - /// The result. - public DelegateResult(TResult result) => Result = result; + public class DelegateResult + { + /// + /// Create an instance of representing an execution which returned + /// + /// The result. + public DelegateResult(TResult result) => Result = result; - /// - /// Create an instance of representing an execution which threw - /// - /// The exception. - public DelegateResult(Exception exception) => Exception = exception; + /// + /// Create an instance of representing an execution which threw + /// + /// The exception. + public DelegateResult(Exception exception) => Exception = exception; - /// - /// The result of executing the delegate. Will be default(TResult) if an exception was thrown. - /// - public TResult Result { get; } + /// + /// The result of executing the delegate. Will be default(TResult) if an exception was thrown. + /// + public TResult Result { get; } - /// - /// Any exception thrown while executing the delegate. Will be null if policy executed without exception. - /// - public Exception Exception { get; } -} \ No newline at end of file + /// + /// Any exception thrown while executing the delegate. Will be null if policy executed without exception. + /// + public Exception Exception { get; } + } +} diff --git a/src/Polly/ExceptionPredicate.cs b/src/Polly/ExceptionPredicate.cs index 4d22a9a39c0..03cc9dfd4d8 100644 --- a/src/Polly/ExceptionPredicate.cs +++ b/src/Polly/ExceptionPredicate.cs @@ -1,10 +1,11 @@ using System; -namespace Polly; - -/// -/// A predicate that can be run against a passed . -/// -/// The passed exception, against which to evaluate the predicate. -/// A matched ; or null, if an exception was not matched. ExceptionPredicate implementations may return the passed Exception , indicating that it matched the predicate. They may also return inner exceptions of the passed Exception , to indicate that the returned inner exception matched the predicate. -public delegate Exception ExceptionPredicate(Exception ex); \ No newline at end of file +namespace Polly +{ + /// + /// A predicate that can be run against a passed . + /// + /// The passed exception, against which to evaluate the predicate. + /// A matched ; or null, if an exception was not matched. ExceptionPredicate implementations may return the passed Exception , indicating that it matched the predicate. They may also return inner exceptions of the passed Exception , to indicate that the returned inner exception matched the predicate. + public delegate Exception ExceptionPredicate(Exception ex); +} \ No newline at end of file diff --git a/src/Polly/ExceptionPredicates.cs b/src/Polly/ExceptionPredicates.cs index 8f63689e2f8..2e81b43efec 100644 --- a/src/Polly/ExceptionPredicates.cs +++ b/src/Polly/ExceptionPredicates.cs @@ -2,32 +2,34 @@ using System.Collections.Generic; using System.Linq; -namespace Polly; - -/// -/// A collection of predicates used to define whether a policy handles a given . -/// -public class ExceptionPredicates +namespace Polly { - private List _predicates; - - internal void Add(ExceptionPredicate predicate) + /// + /// A collection of predicates used to define whether a policy handles a given . + /// + public class ExceptionPredicates { - _predicates = _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. + private List _predicates; - _predicates.Add(predicate); - } + internal void Add(ExceptionPredicate predicate) + { + _predicates = _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. - /// - /// Assess whether the passed , , matches any of the predicates. - /// If the .HandleInner() method was used when configuring the policy, predicates may test whether any inner exceptions of match and may return a matching inner exception. - /// - /// The exception to assess against the predicates. - /// The first exception to match a predicate; or null, if no match is found. - public Exception FirstMatchOrDefault(Exception ex) => _predicates?.Select(predicate => predicate(ex)).FirstOrDefault(e => e != null); + _predicates.Add(predicate); + } + + /// + /// Assess whether the passed , , matches any of the predicates. + /// If the .HandleInner() method was used when configuring the policy, predicates may test whether any inner exceptions of match and may return a matching inner exception. + /// + /// The exception to assess against the predicates. + /// The first exception to match a predicate; or null, if no match is found. + public Exception FirstMatchOrDefault(Exception ex) => _predicates?.Select(predicate => predicate(ex)).FirstOrDefault(e => e != null); + + /// + /// Specifies that no Exception-handling filters are applied or are required. + /// + public static readonly ExceptionPredicates None = new ExceptionPredicates(); + } - /// - /// Specifies that no Exception-handling filters are applied or are required. - /// - public static readonly ExceptionPredicates None = new ExceptionPredicates(); } \ No newline at end of file diff --git a/src/Polly/Fallback/AsyncFallbackEngine.cs b/src/Polly/Fallback/AsyncFallbackEngine.cs index d895ceaf379..51997811bd4 100644 --- a/src/Polly/Fallback/AsyncFallbackEngine.cs +++ b/src/Polly/Fallback/AsyncFallbackEngine.cs @@ -2,48 +2,49 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Fallback; - -internal class AsyncFallbackEngine +namespace Polly.Fallback { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Func, Context, Task> onFallbackAsync, - Func, Context, CancellationToken, Task> fallbackAction, - bool continueOnCapturedContext) + internal class AsyncFallbackEngine { - DelegateResult delegateOutcome; - - try + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Func, Context, Task> onFallbackAsync, + Func, Context, CancellationToken, Task> fallbackAction, + bool continueOnCapturedContext) { - cancellationToken.ThrowIfCancellationRequested(); + DelegateResult delegateOutcome; - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - - if (!shouldHandleResultPredicates.AnyMatch(result)) + try { - return result; - } + cancellationToken.ThrowIfCancellationRequested(); - delegateOutcome = new DelegateResult(result); - } - catch (Exception ex) - { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; + var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + + if (!shouldHandleResultPredicates.AnyMatch(result)) + { + return result; + } + + delegateOutcome = new DelegateResult(result); } + catch (Exception ex) + { + var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - delegateOutcome = new DelegateResult(handledException); - } + delegateOutcome = new DelegateResult(handledException); + } - await onFallbackAsync(delegateOutcome, context).ConfigureAwait(continueOnCapturedContext); + await onFallbackAsync(delegateOutcome, context).ConfigureAwait(continueOnCapturedContext); - return await fallbackAction(delegateOutcome, context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + return await fallbackAction(delegateOutcome, context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Fallback/AsyncFallbackPolicy.cs b/src/Polly/Fallback/AsyncFallbackPolicy.cs index 7c56de0c0e2..11e572a80b4 100644 --- a/src/Polly/Fallback/AsyncFallbackPolicy.cs +++ b/src/Polly/Fallback/AsyncFallbackPolicy.cs @@ -4,83 +4,84 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Fallback; - -/// -/// A fallback policy that can be applied to asynchronous delegates. -/// -public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy +namespace Polly.Fallback { - private Func _onFallbackAsync; - private Func _fallbackAction; - - internal AsyncFallbackPolicy(PolicyBuilder policyBuilder, Func onFallbackAsync, - Func fallbackAction) - : base(policyBuilder) + /// + /// A fallback policy that can be applied to asynchronous delegates. + /// + public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy { - _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } + private Func _onFallbackAsync; + private Func _fallbackAction; - /// - protected override Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - return AsyncFallbackEngine.ImplementationAsync( - async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, ctx) => _onFallbackAsync(outcome.Exception, ctx), - async (outcome, ctx, ct) => - { - await _fallbackAction(outcome.Exception, ctx, ct).ConfigureAwait(continueOnCapturedContext); - return EmptyStruct.Instance; - }, - continueOnCapturedContext); - } + internal AsyncFallbackPolicy(PolicyBuilder policyBuilder, Func onFallbackAsync, + Func fallbackAction) + : base(policyBuilder) + { + _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); + } - /// - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => - throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); -} + /// + protected override Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncFallbackEngine.ImplementationAsync( + async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + (outcome, ctx) => _onFallbackAsync(outcome.Exception, ctx), + async (outcome, ctx, ct) => + { + await _fallbackAction(outcome.Exception, ctx, ct).ConfigureAwait(continueOnCapturedContext); + return EmptyStruct.Instance; + }, + continueOnCapturedContext); + } -/// -/// A fallback policy that can be applied to delegates. -/// -/// The return type of delegates which may be executed through the policy. -public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy -{ - private Func, Context, Task> _onFallbackAsync; - private Func, Context, CancellationToken, Task> _fallbackAction; + /// + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => + throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); + } - internal AsyncFallbackPolicy( - PolicyBuilder policyBuilder, - Func, Context, Task> onFallbackAsync, - Func, Context, CancellationToken, Task> fallbackAction - ) : base(policyBuilder) + /// + /// A fallback policy that can be applied to delegates. + /// + /// The return type of delegates which may be executed through the policy. + public class AsyncFallbackPolicy : AsyncPolicy, IFallbackPolicy { - _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } + private Func, Context, Task> _onFallbackAsync; + private Func, Context, CancellationToken, Task> _fallbackAction; + + internal AsyncFallbackPolicy( + PolicyBuilder policyBuilder, + Func, Context, Task> onFallbackAsync, + Func, Context, CancellationToken, Task> fallbackAction + ) : base(policyBuilder) + { + _onFallbackAsync = onFallbackAsync ?? throw new ArgumentNullException(nameof(onFallbackAsync)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); + } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncFallbackEngine.ImplementationAsync( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _onFallbackAsync, - _fallbackAction, - continueOnCapturedContext); + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncFallbackEngine.ImplementationAsync( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onFallbackAsync, + _fallbackAction, + continueOnCapturedContext); + } } \ No newline at end of file diff --git a/src/Polly/Fallback/AsyncFallbackSyntax.cs b/src/Polly/Fallback/AsyncFallbackSyntax.cs index 20fffb98ccc..338e34f3060 100644 --- a/src/Polly/Fallback/AsyncFallbackSyntax.cs +++ b/src/Polly/Fallback/AsyncFallbackSyntax.cs @@ -4,214 +4,215 @@ using Polly.Fallback; using Polly.Utilities; -namespace Polly; - -/// -/// Fluent API for defining a Fallback . -/// -public static class AsyncFallbackSyntax +namespace Polly { /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, asynchronously calls . - /// - /// The policy builder. - /// The fallback delegate. - /// fallbackAction - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Func doNothing = _ => TaskHelper.EmptyTask; - return policyBuilder.FallbackAsync( - fallbackAction, - doNothing - ); - } - - /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception; then asynchronously calls . + /// Fluent API for defining a Fallback . /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) + public static class AsyncFallbackSyntax { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, ct) => fallbackAction(ct), - (outcome, _) => onFallbackAsync(outcome) - ); + /// + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, asynchronously calls . + /// + /// The policy builder. + /// The fallback delegate. + /// fallbackAction + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Func doNothing = _ => TaskHelper.EmptyTask; + return policyBuilder.FallbackAsync( + fallbackAction, + doNothing + ); + } + + /// + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception; then asynchronously calls . + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, ct) => fallbackAction(ct), + (outcome, _) => onFallbackAsync(outcome) + ); + } + + /// + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); + } + + /// + /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return new AsyncFallbackPolicy(policyBuilder, onFallbackAsync, fallbackAction); + } } /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . + /// Fluent API for defining an async Fallback policy governing executions returning TResult. /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) + public static class AsyncFallbackTResultSyntax { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); - } - - /// - /// Builds an which provides a fallback action if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception, first asynchronously calls with details of the handled exception and execution context; then asynchronously calls . - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func fallbackAction, Func onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return new AsyncFallbackPolicy(policyBuilder, onFallbackAsync, fallbackAction); - } -} - -/// -/// Fluent API for defining an async Fallback policy governing executions returning TResult. -/// -public static class AsyncFallbackTResultSyntax -{ - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue) - { - Func, Task> doNothing = _ => TaskHelper.EmptyTask; - return policyBuilder.FallbackAsync( - _ => Task.FromResult(fallbackValue), - doNothing - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// fallbackAction - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Func, Task> doNothing = _ => TaskHelper.EmptyTask; - return policyBuilder.FallbackAsync( - fallbackAction, - doNothing - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call asynchronously before invoking the fallback delegate. - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Task> onFallbackAsync) - { - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, _) => Task.FromResult(fallbackValue), - (outcome, _) => onFallbackAsync(outcome) - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Task> onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, ct) => fallbackAction(ct), - (outcome, _) => onFallbackAsync(outcome) - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call asynchronously before invoking the fallback delegate. - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Context, Task> onFallbackAsync) - { - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync( - (_, _, _) => Task.FromResult(fallbackValue), - onFallbackAsync - ); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Context, Task> onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); - } - - /// - /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. - /// - /// The policy builder. - /// The fallback delegate. - /// The action to call asynchronously before invoking the fallback delegate. - /// fallbackAction - /// onFallbackAsync - /// The policy instance. - public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, Task> fallbackAction, Func, Context, Task> onFallbackAsync) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); - - return new AsyncFallbackPolicy( - policyBuilder, - onFallbackAsync, - fallbackAction); + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue) + { + Func, Task> doNothing = _ => TaskHelper.EmptyTask; + return policyBuilder.FallbackAsync( + _ => Task.FromResult(fallbackValue), + doNothing + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// fallbackAction + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Func, Task> doNothing = _ => TaskHelper.EmptyTask; + return policyBuilder.FallbackAsync( + fallbackAction, + doNothing + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call asynchronously before invoking the fallback delegate. + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Task> onFallbackAsync) + { + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, _) => Task.FromResult(fallbackValue), + (outcome, _) => onFallbackAsync(outcome) + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result; then asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Task> onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, ct) => fallbackAction(ct), + (outcome, _) => onFallbackAsync(outcome) + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call asynchronously before invoking the fallback delegate. + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, TResult fallbackValue, Func, Context, Task> onFallbackAsync) + { + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync( + (_, _, _) => Task.FromResult(fallbackValue), + onFallbackAsync + ); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func> fallbackAction, Func, Context, Task> onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return policyBuilder.FallbackAsync((_, ctx, ct) => fallbackAction(ctx, ct), onFallbackAsync); + } + + /// + /// Builds an which provides a fallback value if the main execution fails. Executes the main delegate asynchronously, but if this throws a handled exception or raises a handled result, first asynchronously calls with details of the handled exception or result and the execution context; then asynchronously calls and returns its result. + /// + /// The policy builder. + /// The fallback delegate. + /// The action to call asynchronously before invoking the fallback delegate. + /// fallbackAction + /// onFallbackAsync + /// The policy instance. + public static AsyncFallbackPolicy FallbackAsync(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, Task> fallbackAction, Func, Context, Task> onFallbackAsync) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallbackAsync == null) throw new ArgumentNullException(nameof(onFallbackAsync)); + + return new AsyncFallbackPolicy( + policyBuilder, + onFallbackAsync, + fallbackAction); + } } } \ No newline at end of file diff --git a/src/Polly/Fallback/FallbackEngine.cs b/src/Polly/Fallback/FallbackEngine.cs index fe51ba4e4da..65ebee78cc8 100644 --- a/src/Polly/Fallback/FallbackEngine.cs +++ b/src/Polly/Fallback/FallbackEngine.cs @@ -1,47 +1,48 @@ using System; using System.Threading; -namespace Polly.Fallback; - -internal static class FallbackEngine +namespace Polly.Fallback { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldHandleExceptionPredicates, - ResultPredicates shouldHandleResultPredicates, - Action, Context> onFallback, - Func, Context, CancellationToken, TResult> fallbackAction) + internal static class FallbackEngine { - DelegateResult delegateOutcome; - - try + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldHandleExceptionPredicates, + ResultPredicates shouldHandleResultPredicates, + Action, Context> onFallback, + Func, Context, CancellationToken, TResult> fallbackAction) { - cancellationToken.ThrowIfCancellationRequested(); + DelegateResult delegateOutcome; - var result = action(context, cancellationToken); - - if (!shouldHandleResultPredicates.AnyMatch(result)) + try { - return result; - } + cancellationToken.ThrowIfCancellationRequested(); - delegateOutcome = new DelegateResult(result); - } - catch (Exception ex) - { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; + var result = action(context, cancellationToken); + + if (!shouldHandleResultPredicates.AnyMatch(result)) + { + return result; + } + + delegateOutcome = new DelegateResult(result); } + catch (Exception ex) + { + var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - delegateOutcome = new DelegateResult(handledException); - } + delegateOutcome = new DelegateResult(handledException); + } - onFallback(delegateOutcome, context); + onFallback(delegateOutcome, context); - return fallbackAction(delegateOutcome, context, cancellationToken); + return fallbackAction(delegateOutcome, context, cancellationToken); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Fallback/FallbackPolicy.cs b/src/Polly/Fallback/FallbackPolicy.cs index 3e213cd357e..b5dfb047b0c 100644 --- a/src/Polly/Fallback/FallbackPolicy.cs +++ b/src/Polly/Fallback/FallbackPolicy.cs @@ -3,70 +3,71 @@ using System.Threading; using Polly.Utilities; -namespace Polly.Fallback; - -/// -/// A fallback policy that can be applied to delegates. -/// -public class FallbackPolicy : Policy, IFallbackPolicy +namespace Polly.Fallback { - private Action _onFallback; - private Action _fallbackAction; - - internal FallbackPolicy( - PolicyBuilder policyBuilder, - Action onFallback, - Action fallbackAction) - : base(policyBuilder) + /// + /// A fallback policy that can be applied to delegates. + /// + public class FallbackPolicy : Policy, IFallbackPolicy { - _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } + private Action _onFallback; + private Action _fallbackAction; - /// - [DebuggerStepThrough] - protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) - => FallbackEngine.Implementation( - (ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, ctx) => _onFallback(outcome.Exception, ctx), - (outcome, ctx, ct) => { _fallbackAction(outcome.Exception, ctx, ct); return EmptyStruct.Instance; }); + internal FallbackPolicy( + PolicyBuilder policyBuilder, + Action onFallback, + Action fallbackAction) + : base(policyBuilder) + { + _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); + } - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); -} + /// + [DebuggerStepThrough] + protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) + => FallbackEngine.Implementation( + (ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + (outcome, ctx) => _onFallback(outcome.Exception, ctx), + (outcome, ctx, ct) => { _fallbackAction(outcome.Exception, ctx, ct); return EmptyStruct.Instance; }); -/// -/// A fallback policy that can be applied to delegates returning a value of type . -/// -public class FallbackPolicy : Policy, IFallbackPolicy -{ - private Action, Context> _onFallback; - private Func, Context, CancellationToken, TResult> _fallbackAction; + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);"); + } - internal FallbackPolicy( - PolicyBuilder policyBuilder, - Action, Context> onFallback, - Func, Context, CancellationToken, TResult> fallbackAction - ) : base(policyBuilder) + /// + /// A fallback policy that can be applied to delegates returning a value of type . + /// + public class FallbackPolicy : Policy, IFallbackPolicy { - _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); - _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); - } + private Action, Context> _onFallback; + private Func, Context, CancellationToken, TResult> _fallbackAction; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => FallbackEngine.Implementation( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _onFallback, - _fallbackAction); + internal FallbackPolicy( + PolicyBuilder policyBuilder, + Action, Context> onFallback, + Func, Context, CancellationToken, TResult> fallbackAction + ) : base(policyBuilder) + { + _onFallback = onFallback ?? throw new ArgumentNullException(nameof(onFallback)); + _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction)); + } + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => FallbackEngine.Implementation( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onFallback, + _fallbackAction); + } } \ No newline at end of file diff --git a/src/Polly/Fallback/FallbackSyntax.cs b/src/Polly/Fallback/FallbackSyntax.cs index 8fdea6d9dac..708e72250b1 100644 --- a/src/Polly/Fallback/FallbackSyntax.cs +++ b/src/Polly/Fallback/FallbackSyntax.cs @@ -2,295 +2,296 @@ using System.Threading; using Polly.Fallback; -namespace Polly; - -/// -/// Fluent API for defining a Fallback policy. -/// -public static class FallbackSyntax +namespace Polly { /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackAction(), (exception, _) => onFallback(exception)); - } - - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . + /// Fluent API for defining a Fallback policy. /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + public static class FallbackSyntax { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (exception, _) => onFallback(exception)); + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, calls . + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackAction(), (exception, _) => onFallback(exception)); + } + + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (exception, _) => onFallback(exception)); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); + } + + /// + /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return new FallbackPolicy( + policyBuilder, + onFallback, + fallbackAction); + } } /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . + /// Fluent API for defining a Fallback policy governing executions returning TResult. /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) + public static class FallbackTResultSyntax { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); - } - - /// - /// Builds a which provides a fallback action if the main execution fails. Executes the main delegate, but if this throws a handled exception, first calls with details of the handled exception and the execution context; then calls . - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Action fallbackAction, Action onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return new FallbackPolicy( - policyBuilder, - onFallback, - fallbackAction); + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, returns instead. + /// + /// The policy builder. + /// The fallback value to provide. + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue) + { + Action> doNothing = _ => { }; + return policyBuilder.Fallback(() => fallbackValue, doNothing); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action> doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// fallbackAction + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + + Action> doNothing = _ => { }; + return policyBuilder.Fallback(fallbackAction, doNothing); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call before invoking the fallback delegate. + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action> onFallback) + { + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackValue, (outcome, _) => onFallback(outcome)); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackAction(), (outcome, _) => onFallback(outcome)); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (outcome, _) => onFallback(outcome)); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then returns . + /// + /// The policy builder. + /// The fallback value to provide. + /// The action to call before invoking the fallback delegate. + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action, Context> onFallback) + { + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, _, _) => fallbackValue, onFallback); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); + } + + /// + /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. + /// + /// The policy builder. + /// The fallback action. + /// The action to call before invoking the fallback delegate. + /// fallbackAction + /// onFallback + /// The policy instance. + public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, TResult> fallbackAction, Action, Context> onFallback) + { + if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); + if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); + + return new FallbackPolicy( + policyBuilder, + onFallback, + fallbackAction); + } } } - -/// -/// Fluent API for defining a Fallback policy governing executions returning TResult. -/// -public static class FallbackTResultSyntax -{ - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, returns instead. - /// - /// The policy builder. - /// The fallback value to provide. - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue) - { - Action> doNothing = _ => { }; - return policyBuilder.Fallback(() => fallbackValue, doNothing); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action> doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// fallbackAction - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - - Action> doNothing = _ => { }; - return policyBuilder.Fallback(fallbackAction, doNothing); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call before invoking the fallback delegate. - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action> onFallback) - { - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackValue, (outcome, _) => onFallback(outcome)); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackAction(), (outcome, _) => onFallback(outcome)); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, ct) => fallbackAction(ct), (outcome, _) => onFallback(outcome)); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then returns . - /// - /// The policy builder. - /// The fallback value to provide. - /// The action to call before invoking the fallback delegate. - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, TResult fallbackValue, Action, Context> onFallback) - { - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, _, _) => fallbackValue, onFallback); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, _) => fallbackAction(ctx), onFallback); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func fallbackAction, Action, Context> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return policyBuilder.Fallback((_, ctx, ct) => fallbackAction(ctx, ct), onFallback); - } - - /// - /// Builds a which provides a fallback value if the main execution fails. Executes the main delegate, but if this throws a handled exception or raises a handled result, first calls with details of the handled exception or result and the execution context; then calls and returns its result. - /// - /// The policy builder. - /// The fallback action. - /// The action to call before invoking the fallback delegate. - /// fallbackAction - /// onFallback - /// The policy instance. - public static FallbackPolicy Fallback(this PolicyBuilder policyBuilder, Func, Context, CancellationToken, TResult> fallbackAction, Action, Context> onFallback) - { - if (fallbackAction == null) throw new ArgumentNullException(nameof(fallbackAction)); - if (onFallback == null) throw new ArgumentNullException(nameof(onFallback)); - - return new FallbackPolicy( - policyBuilder, - onFallback, - fallbackAction); - } -} \ No newline at end of file diff --git a/src/Polly/Fallback/IFallbackPolicy.cs b/src/Polly/Fallback/IFallbackPolicy.cs index 7ef0c7a1983..894e2590cc6 100644 --- a/src/Polly/Fallback/IFallbackPolicy.cs +++ b/src/Polly/Fallback/IFallbackPolicy.cs @@ -1,15 +1,17 @@ -namespace Polly.Fallback; - -/// -/// Defines properties and methods common to all Fallback policies. -/// -public interface IFallbackPolicy : IsPolicy +namespace Polly.Fallback { -} + /// + /// Defines properties and methods common to all Fallback policies. + /// -/// -/// Defines properties and methods common to all Fallback policies generic-typed for executions returning results of type . -/// -public interface IFallbackPolicy : IFallbackPolicy -{ -} \ No newline at end of file + public interface IFallbackPolicy : IsPolicy + { + } + + /// + /// Defines properties and methods common to all Fallback policies generic-typed for executions returning results of type . + /// + public interface IFallbackPolicy : IFallbackPolicy + { + } +} diff --git a/src/Polly/IAsyncPolicy.Extensions.cs b/src/Polly/IAsyncPolicy.Extensions.cs index 5425bf0b493..92f8fc22911 100644 --- a/src/Polly/IAsyncPolicy.Extensions.cs +++ b/src/Polly/IAsyncPolicy.Extensions.cs @@ -1,16 +1,18 @@ -namespace Polly; - -/// -/// Contains extensions methods on -/// -public static class IAsyncPolicyExtensions +namespace Polly { /// - /// Converts a non-generic into a generic for handling only executions returning . + /// Contains extensions methods on /// - /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . - /// The non-generic to convert to a generic . - /// A generic version of the supplied non-generic . - public static IAsyncPolicy AsAsyncPolicy(this IAsyncPolicy policy) - => policy.WrapAsync(Policy.NoOpAsync()); -} \ No newline at end of file + public static class IAsyncPolicyExtensions + { + /// + /// Converts a non-generic into a generic for handling only executions returning . + /// + /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . + /// The non-generic to convert to a generic . + /// A generic version of the supplied non-generic . + public static IAsyncPolicy AsAsyncPolicy(this IAsyncPolicy policy) + => policy.WrapAsync(Policy.NoOpAsync()); + } + +} diff --git a/src/Polly/IAsyncPolicy.TResult.cs b/src/Polly/IAsyncPolicy.TResult.cs index c3ed691b2b9..efc8ffab022 100644 --- a/src/Polly/IAsyncPolicy.TResult.cs +++ b/src/Polly/IAsyncPolicy.TResult.cs @@ -3,182 +3,183 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly; - -/// -/// An interface defining all executions available on an asynchronous policy generic-typed for executions returning results of type . -/// -/// The type of the result of funcs executed through the Policy. -public interface IAsyncPolicy : IsPolicy +namespace Polly { /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy WithPolicyKey(string policyKey); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The value returned by the action - Task ExecuteAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); -} \ No newline at end of file + /// An interface defining all executions available on an asynchronous policy generic-typed for executions returning results of type . + /// + /// The type of the result of funcs executed through the Policy. + public interface IAsyncPolicy : IsPolicy + { + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy WithPolicyKey(string policyKey); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The value returned by the action + Task ExecuteAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + } +} diff --git a/src/Polly/IAsyncPolicy.cs b/src/Polly/IAsyncPolicy.cs index 675b5e9bdcc..34da99e71cc 100644 --- a/src/Polly/IAsyncPolicy.cs +++ b/src/Polly/IAsyncPolicy.cs @@ -3,346 +3,347 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly; - -/// -/// An interface defining all executions available on a non-generic, asynchronous policy -/// -public interface IAsyncPolicy : IsPolicy +namespace Polly { /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - IAsyncPolicy WithPolicyKey(string policyKey); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - Task ExecuteAsync(Func action); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - Task ExecuteAsync(Func action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - Task ExecuteAsync(Func action, Context context); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAsync(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The value returned by the action - Task ExecuteAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The value returned by the action - /// contextData - Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. - /// The value returned by the action - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// The captured result - Task ExecuteAndCaptureAsync(Func action); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - Task ExecuteAndCaptureAsync(Func action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - Task ExecuteAndCaptureAsync(Func action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// contextData - /// The captured result - Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// contextData - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// Whether to continue on a captured synchronization context. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// The captured result - /// contextData - Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); - - /// - /// Executes the specified asynchronous action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. - /// Whether to continue on a captured synchronization context. - /// The captured result - /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. - Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); -} \ No newline at end of file + /// An interface defining all executions available on a non-generic, asynchronous policy + /// + public interface IAsyncPolicy : IsPolicy + { + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + IAsyncPolicy WithPolicyKey(string policyKey); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + Task ExecuteAsync(Func action); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + Task ExecuteAsync(Func action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + Task ExecuteAsync(Func action, Context context); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAsync(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + Task ExecuteAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The value returned by the action + Task ExecuteAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The value returned by the action + /// contextData + Task ExecuteAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries. + /// The value returned by the action + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// The captured result + Task ExecuteAndCaptureAsync(Func action); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + Task ExecuteAndCaptureAsync(Func action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + Task ExecuteAndCaptureAsync(Func action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// contextData + /// The captured result + Task ExecuteAndCaptureAsync(Func action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task ExecuteAndCaptureAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// contextData + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// Whether to continue on a captured synchronization context. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// The captured result + /// contextData + Task> ExecuteAndCaptureAsync(Func> action, IDictionary contextData, CancellationToken cancellationToken, bool continueOnCapturedContext); + + /// + /// Executes the specified asynchronous action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. + /// Whether to continue on a captured synchronization context. + /// The captured result + /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. + Task> ExecuteAndCaptureAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext); + } +} diff --git a/src/Polly/ISyncPolicy.Extensions.cs b/src/Polly/ISyncPolicy.Extensions.cs index 60c04f427cf..af4d41766f0 100644 --- a/src/Polly/ISyncPolicy.Extensions.cs +++ b/src/Polly/ISyncPolicy.Extensions.cs @@ -1,16 +1,18 @@ -namespace Polly; - -/// -/// Contains extensions methods on -/// -public static class ISyncPolicyExtensions +namespace Polly { /// - /// Converts a non-generic into a generic for handling only executions returning . + /// Contains extensions methods on /// - /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . - /// The non-generic to convert to a generic . - /// A generic version of the supplied non-generic . - public static ISyncPolicy AsPolicy(this ISyncPolicy policy) - => policy.Wrap(Policy.NoOp()); -} \ No newline at end of file + public static class ISyncPolicyExtensions + { + /// + /// Converts a non-generic into a generic for handling only executions returning . + /// + /// This method allows you to convert a non-generic into a generic for contexts such as variables or parameters which may explicitly require a generic . + /// The non-generic to convert to a generic . + /// A generic version of the supplied non-generic . + public static ISyncPolicy AsPolicy(this ISyncPolicy policy) + => policy.Wrap(Policy.NoOp()); + } + +} diff --git a/src/Polly/ISyncPolicy.TResult.cs b/src/Polly/ISyncPolicy.TResult.cs index 9d478058352..6a8cb85c110 100644 --- a/src/Polly/ISyncPolicy.TResult.cs +++ b/src/Polly/ISyncPolicy.TResult.cs @@ -2,128 +2,130 @@ using System.Collections.Generic; using System.Threading; -namespace Polly; - -/// -/// An interface defining all executions available on a synchronous policy generic-typed for executions returning results of type . -/// -/// The type of the result of funcs executed through the Policy. -public interface ISyncPolicy : IsPolicy +namespace Polly { /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. + /// An interface defining all executions available on a synchronous policy generic-typed for executions returning results of type . /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy WithPolicyKey(string policyKey); + /// The type of the result of funcs executed through the Policy. - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The action to perform. - /// The value returned by the action - TResult Execute(Func action); + public interface ISyncPolicy : IsPolicy + { + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + ISyncPolicy WithPolicyKey(string policyKey); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, IDictionary contextData); + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The action to perform. + /// The value returned by the action + TResult Execute(Func action); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// context - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, Context context); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, IDictionary contextData); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// context + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, Context context); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, Context context, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - PolicyResult ExecuteAndCapture(Func action); + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, Context context, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + PolicyResult ExecuteAndCapture(Func action); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context); + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); -} \ No newline at end of file + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); + } +} diff --git a/src/Polly/ISyncPolicy.cs b/src/Polly/ISyncPolicy.cs index bda72a4c225..bbb750fa29b 100644 --- a/src/Polly/ISyncPolicy.cs +++ b/src/Polly/ISyncPolicy.cs @@ -2,230 +2,231 @@ using System.Collections.Generic; using System.Threading; -namespace Polly; - -/// -/// An interface defining all executions available on a non-generic, synchronous policy -/// -public interface ISyncPolicy : IsPolicy +namespace Polly { /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy WithPolicyKey(string policyKey); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - void Execute(Action action); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - void Execute(Action action, IDictionary contextData); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - void Execute(Action action, Context context); - - /// - /// - /// - /// - /// - void Execute(Action action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// contextData - void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - void Execute(Action action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The type of the Result. - /// The action to perform. - /// The value returned by the action - TResult Execute(Func action); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, IDictionary contextData); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - TResult Execute(Func action, Context context); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - TResult Execute(Func action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - PolicyResult ExecuteAndCapture(Action action); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Action action, IDictionary contextData); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - PolicyResult ExecuteAndCapture(Action action, Context context); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - PolicyResult ExecuteAndCapture(Func action); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The type of the t result. - /// The action to perform. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); -} \ No newline at end of file + /// An interface defining all executions available on a non-generic, synchronous policy + /// + public interface ISyncPolicy : IsPolicy + { + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + ISyncPolicy WithPolicyKey(string policyKey); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + void Execute(Action action); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + void Execute(Action action, IDictionary contextData); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + void Execute(Action action, Context context); + + /// + /// + /// + /// + /// + void Execute(Action action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// contextData + void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + void Execute(Action action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The type of the Result. + /// The action to perform. + /// The value returned by the action + TResult Execute(Func action); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, IDictionary contextData); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + TResult Execute(Func action, Context context); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + TResult Execute(Func action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + PolicyResult ExecuteAndCapture(Action action); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Action action, IDictionary contextData); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + PolicyResult ExecuteAndCapture(Action action, Context context); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + PolicyResult ExecuteAndCapture(Func action); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The type of the t result. + /// The action to perform. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken); + } +} diff --git a/src/Polly/IsPolicy.cs b/src/Polly/IsPolicy.cs index 32b4f2f148e..ed3211a8983 100644 --- a/src/Polly/IsPolicy.cs +++ b/src/Polly/IsPolicy.cs @@ -1,12 +1,13 @@ -namespace Polly; - -/// -/// A marker interface identifying Polly policies of all types, and containing properties common to all policies -/// -public interface IsPolicy -{ +namespace Polly +{ /// - /// A key intended to be unique to each policy instance, which is passed with executions as the property. + /// A marker interface identifying Polly policies of all types, and containing properties common to all policies /// - string PolicyKey { get; } -} \ No newline at end of file + public interface IsPolicy + { + /// + /// A key intended to be unique to each policy instance, which is passed with executions as the property. + /// + string PolicyKey { get; } + } +} diff --git a/src/Polly/NoOp/AsyncNoOpPolicy.cs b/src/Polly/NoOp/AsyncNoOpPolicy.cs index ac14f676951..305f4f2563a 100644 --- a/src/Polly/NoOp/AsyncNoOpPolicy.cs +++ b/src/Polly/NoOp/AsyncNoOpPolicy.cs @@ -3,36 +3,37 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.NoOp; - -/// -/// A noop policy that can be applied to asynchronous delegates. -/// -public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy +namespace Polly.NoOp { - internal AsyncNoOpPolicy() + /// + /// A noop policy that can be applied to asynchronous delegates. + /// + public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy { - } + internal AsyncNoOpPolicy() + { + } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); -} + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); + } -/// -/// A noop policy that can be applied to asynchronous delegates returning a value of type . -/// -public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy -{ - internal AsyncNoOpPolicy() + /// + /// A noop policy that can be applied to asynchronous delegates returning a value of type . + /// + public class AsyncNoOpPolicy : AsyncPolicy, INoOpPolicy { - } + internal AsyncNoOpPolicy() + { + } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => NoOpEngine.ImplementationAsync(action, context, cancellationToken, continueOnCapturedContext); + } } \ No newline at end of file diff --git a/src/Polly/NoOp/AsyncNoOpSyntax.cs b/src/Polly/NoOp/AsyncNoOpSyntax.cs index efd2a8e4f6c..3ad872db506 100644 --- a/src/Polly/NoOp/AsyncNoOpSyntax.cs +++ b/src/Polly/NoOp/AsyncNoOpSyntax.cs @@ -1,12 +1,13 @@ using Polly.NoOp; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The policy instance. - public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); -} \ No newline at end of file + public partial class Policy + { + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The policy instance. + public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); + } +} diff --git a/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs b/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs index bd4c0f7f012..508ef723174 100644 --- a/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs +++ b/src/Polly/NoOp/AsyncNoOpTResultSyntax.cs @@ -1,13 +1,14 @@ using Polly.NoOp; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The type of return values this policy will handle. - /// The policy instance. - public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); -} \ No newline at end of file + public partial class Policy + { + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The type of return values this policy will handle. + /// The policy instance. + public static AsyncNoOpPolicy NoOpAsync() => new AsyncNoOpPolicy(); + } +} diff --git a/src/Polly/NoOp/INoOpPolicy.cs b/src/Polly/NoOp/INoOpPolicy.cs index cd8e7e4b957..b32f4cfe177 100644 --- a/src/Polly/NoOp/INoOpPolicy.cs +++ b/src/Polly/NoOp/INoOpPolicy.cs @@ -1,15 +1,17 @@ -namespace Polly.NoOp; - -/// -/// Defines properties and methods common to all NoOp policies. -/// -public interface INoOpPolicy : IsPolicy +namespace Polly.NoOp { -} + /// + /// Defines properties and methods common to all NoOp policies. + /// -/// -/// Defines properties and methods common to all NoOp policies generic-typed for executions returning results of type . -/// -public interface INoOpPolicy : INoOpPolicy -{ -} \ No newline at end of file + public interface INoOpPolicy : IsPolicy + { + } + + /// + /// Defines properties and methods common to all NoOp policies generic-typed for executions returning results of type . + /// + public interface INoOpPolicy : INoOpPolicy + { + } +} diff --git a/src/Polly/NoOp/NoOpEngine.cs b/src/Polly/NoOp/NoOpEngine.cs index 54869449daa..84c7c450623 100644 --- a/src/Polly/NoOp/NoOpEngine.cs +++ b/src/Polly/NoOp/NoOpEngine.cs @@ -1,10 +1,11 @@ using System; using System.Threading; -namespace Polly.NoOp; - -internal static partial class NoOpEngine +namespace Polly.NoOp { - internal static TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => action(context, cancellationToken); -} \ No newline at end of file + internal static partial class NoOpEngine + { + internal static TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => action(context, cancellationToken); + } +} diff --git a/src/Polly/NoOp/NoOpEngineAsync.cs b/src/Polly/NoOp/NoOpEngineAsync.cs index df85c560c0d..bd4f1a5db6f 100644 --- a/src/Polly/NoOp/NoOpEngineAsync.cs +++ b/src/Polly/NoOp/NoOpEngineAsync.cs @@ -2,10 +2,11 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.NoOp; - -internal static partial class NoOpEngine +namespace Polly.NoOp { - internal static async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) - => await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); -} \ No newline at end of file + internal static partial class NoOpEngine + { + internal static async Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) + => await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } +} diff --git a/src/Polly/NoOp/NoOpPolicy.cs b/src/Polly/NoOp/NoOpPolicy.cs index 4717a629c0b..655ec0368b7 100644 --- a/src/Polly/NoOp/NoOpPolicy.cs +++ b/src/Polly/NoOp/NoOpPolicy.cs @@ -2,35 +2,36 @@ using System.Diagnostics; using System.Threading; -namespace Polly.NoOp; - -/// -/// A no op policy that can be applied to delegates. -/// -public class NoOpPolicy : Policy, INoOpPolicy +namespace Polly.NoOp { - internal NoOpPolicy() + /// + /// A no op policy that can be applied to delegates. + /// + public class NoOpPolicy : Policy, INoOpPolicy { - } + internal NoOpPolicy() + { + } - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => NoOpEngine.Implementation(action, context, cancellationToken); -} + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => NoOpEngine.Implementation(action, context, cancellationToken); + } -/// -/// A no op policy that can be applied to delegates returning a value of type -/// -/// The type of return values this policy will handle. -public class NoOpPolicy : Policy, INoOpPolicy -{ - internal NoOpPolicy() + /// + /// A no op policy that can be applied to delegates returning a value of type + /// + /// The type of return values this policy will handle. + public class NoOpPolicy : Policy, INoOpPolicy { - } + internal NoOpPolicy() + { + } - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => NoOpEngine.Implementation(action, context, cancellationToken); -} \ No newline at end of file + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => NoOpEngine.Implementation(action, context, cancellationToken); + } +} diff --git a/src/Polly/NoOp/NoOpSyntax.cs b/src/Polly/NoOp/NoOpSyntax.cs index af780b9a818..659e893e6bd 100644 --- a/src/Polly/NoOp/NoOpSyntax.cs +++ b/src/Polly/NoOp/NoOpSyntax.cs @@ -1,12 +1,13 @@ using Polly.NoOp; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The policy instance. - public static NoOpPolicy NoOp() => new NoOpPolicy(); -} \ No newline at end of file + public partial class Policy + { + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The policy instance. + public static NoOpPolicy NoOp() => new NoOpPolicy(); + } +} diff --git a/src/Polly/NoOp/NoOpTResultSyntax.cs b/src/Polly/NoOp/NoOpTResultSyntax.cs index 920c8b7c94a..11bc9eefb54 100644 --- a/src/Polly/NoOp/NoOpTResultSyntax.cs +++ b/src/Polly/NoOp/NoOpTResultSyntax.cs @@ -1,13 +1,14 @@ using Polly.NoOp; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a NoOp that will execute without any custom behavior. - /// - /// The type of return values this policy will handle. - /// The policy instance. - public static NoOpPolicy NoOp() => new NoOpPolicy(); -} \ No newline at end of file + public partial class Policy + { + /// + /// Builds a NoOp that will execute without any custom behavior. + /// + /// The type of return values this policy will handle. + /// The policy instance. + public static NoOpPolicy NoOp() => new NoOpPolicy(); + } +} diff --git a/src/Polly/Policy.ContextAndKeys.cs b/src/Polly/Policy.ContextAndKeys.cs index 7c9942566ea..bb72a103316 100644 --- a/src/Polly/Policy.ContextAndKeys.cs +++ b/src/Polly/Policy.ContextAndKeys.cs @@ -1,59 +1,60 @@ -namespace Polly; - -public abstract partial class Policy +namespace Polly { - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public Policy WithPolicyKey(string policyKey) + public abstract partial class Policy { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public Policy WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } + + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } } - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) + public abstract partial class Policy { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + public Policy WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } + + /// + /// Sets the PolicyKey for this instance. + /// Must be called before the policy is first used. Can only be set once. + /// + /// The unique, used-definable key to assign to this instance. + ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) + { + if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; + + policyKeyInternal = policyKey; + return this; + } } } - -public abstract partial class Policy -{ - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - public Policy WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } - - /// - /// Sets the PolicyKey for this instance. - /// Must be called before the policy is first used. Can only be set once. - /// - /// The unique, used-definable key to assign to this instance. - ISyncPolicy ISyncPolicy.WithPolicyKey(string policyKey) - { - if (policyKeyInternal != null) throw PolicyKeyMustBeImmutableException; - - policyKeyInternal = policyKey; - return this; - } -} \ No newline at end of file diff --git a/src/Polly/Policy.ExecuteOverloads.cs b/src/Polly/Policy.ExecuteOverloads.cs index 2ddd48dea00..afbfb91ba0e 100644 --- a/src/Polly/Policy.ExecuteOverloads.cs +++ b/src/Polly/Policy.ExecuteOverloads.cs @@ -3,332 +3,333 @@ using System.Diagnostics; using System.Threading; -namespace Polly; - -public abstract partial class Policy : ISyncPolicy +namespace Polly { - #region Execute overloads - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - [DebuggerStepThrough] - public void Execute(Action action) - => Execute((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - [DebuggerStepThrough] - public void Execute(Action action, IDictionary contextData) - => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - [DebuggerStepThrough] - public void Execute(Action action, Context context) - => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy - /// - /// - /// - [DebuggerStepThrough] - public void Execute(Action action, CancellationToken cancellationToken) - => Execute((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// contextData - [DebuggerStepThrough] - public void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken) - => Execute(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - [DebuggerStepThrough] - public void Execute(Action action, Context context, CancellationToken cancellationToken) + public abstract partial class Policy : ISyncPolicy { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); - - try + #region Execute overloads + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + [DebuggerStepThrough] + public void Execute(Action action) + => Execute((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + [DebuggerStepThrough] + public void Execute(Action action, IDictionary contextData) + => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + [DebuggerStepThrough] + public void Execute(Action action, Context context) + => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy + /// + /// + /// + [DebuggerStepThrough] + public void Execute(Action action, CancellationToken cancellationToken) + => Execute((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// contextData + [DebuggerStepThrough] + public void Execute(Action action, IDictionary contextData, CancellationToken cancellationToken) + => Execute(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + [DebuggerStepThrough] + public void Execute(Action action, Context context, CancellationToken cancellationToken) { - Implementation(action, context, cancellationToken); + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + + try + { + Implementation(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } - } - #region Overloads method-generic in TResult - - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The type of the Result. - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action) - => Execute((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData) - => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, Context context) - => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, CancellationToken cancellationToken) - => Execute((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) - => Execute(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The type of the result. - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, Context context, CancellationToken cancellationToken) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); - - try + #region Overloads method-generic in TResult + + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The type of the Result. + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action) + => Execute((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData) + => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, Context context) + => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, CancellationToken cancellationToken) + => Execute((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) + => Execute(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The type of the result. + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, Context context, CancellationToken cancellationToken) { - return Implementation(action, context, cancellationToken); + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + + try + { + return Implementation(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } - finally - { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } - } - #endregion - - #endregion - - #region ExecuteAndCapture overloads - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action) - => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData) - => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, Context context) - => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken) - => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCapture(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - Execute(action, context, cancellationToken); - return PolicyResult.Successful(context); - } - catch (Exception exception) + #endregion + + #endregion + + #region ExecuteAndCapture overloads + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action) + => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData) + => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, Context context) + => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken) + => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCapture(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Action action, Context context, CancellationToken cancellationToken) { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + if (context == null) throw new ArgumentNullException(nameof(context)); + + try + { + Execute(action, context, cancellationToken); + return PolicyResult.Successful(context); + } + catch (Exception exception) + { + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + } } - } - #region Overloads method-generic in TResult - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action) - => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) - => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context) - => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The type of the t result. - /// The action to perform. - /// The cancellation token. - /// The captured result - public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) - => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The type of the result. - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCapture(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - - try - { - return PolicyResult.Successful(Execute(action, context, cancellationToken), context); - } - catch (Exception exception) + #region Overloads method-generic in TResult + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action) + => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) + => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context) + => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The type of the t result. + /// The action to perform. + /// The cancellation token. + /// The captured result + public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) + => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The type of the result. + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCapture(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + if (context == null) throw new ArgumentNullException(nameof(context)); + + try + { + return PolicyResult.Successful(Execute(action, context, cancellationToken), context); + } + catch (Exception exception) + { + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); + } } - } - #endregion + #endregion - #endregion + #endregion -} \ No newline at end of file + } +} diff --git a/src/Polly/Policy.HandleSyntax.cs b/src/Polly/Policy.HandleSyntax.cs index 3223903a944..8128ca0f497 100644 --- a/src/Polly/Policy.HandleSyntax.cs +++ b/src/Polly/Policy.HandleSyntax.cs @@ -1,112 +1,113 @@ using System; -namespace Polly; - -public partial class Policy +namespace Polly { + public partial class Policy + { - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder Handle() where TException : Exception - => new PolicyBuilder(exception => exception is TException ? exception : null); + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder Handle() where TException : Exception + => new PolicyBuilder(exception => exception is TException ? exception : null); - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner() where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner() where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); - /// - /// Specifies the type of return result that this policy can handle with additional filters on the result. - /// - /// The type of return values this policy will handle. - /// The predicate to filter results this policy will handle. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(Func resultPredicate) - => new PolicyBuilder(resultPredicate); + /// + /// Specifies the type of return result that this policy can handle with additional filters on the result. + /// + /// The type of return values this policy will handle. + /// The predicate to filter results this policy will handle. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(Func resultPredicate) + => new PolicyBuilder(resultPredicate); - /// - /// Specifies the type of return result that this policy can handle, and a result value which the policy will handle. - /// - /// The type of return values this policy will handle. - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(TResult result) - => HandleResult(new Func(r => (r != null && r.Equals(result)) || (r == null && result == null))); -} + /// + /// Specifies the type of return result that this policy can handle, and a result value which the policy will handle. + /// + /// The type of return values this policy will handle. + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(TResult result) + => HandleResult(new Func(r => (r != null && r.Equals(result)) || (r == null && result == null))); + } -public partial class Policy -{ - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance. - public static PolicyBuilder Handle() where TException : Exception - => new PolicyBuilder(exception => exception is TException ? exception : null); + public partial class Policy + { + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance. + public static PolicyBuilder Handle() where TException : Exception + => new PolicyBuilder(exception => exception is TException ? exception : null); - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance. - public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance. + public static PolicyBuilder Handle(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner() where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner() where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException)); - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception - => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public static PolicyBuilder HandleInner(Func exceptionPredicate) where TException : Exception + => new PolicyBuilder(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); - /// - /// Specifies a filter on the return result values that this strongly-typed generic policy will handle. - /// - /// The predicate to filter the results this policy will handle. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(Func resultPredicate) - => new PolicyBuilder(resultPredicate); + /// + /// Specifies a filter on the return result values that this strongly-typed generic policy will handle. + /// + /// The predicate to filter the results this policy will handle. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(Func resultPredicate) + => new PolicyBuilder(resultPredicate); - /// - /// Specifies a return result value which the strongly-typed generic policy will handle. - /// - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public static PolicyBuilder HandleResult(TResult result) - => HandleResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); -} \ No newline at end of file + /// + /// Specifies a return result value which the strongly-typed generic policy will handle. + /// + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public static PolicyBuilder HandleResult(TResult result) + => HandleResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); + } +} diff --git a/src/Polly/Policy.SyncGenericImplementation.cs b/src/Polly/Policy.SyncGenericImplementation.cs index f8a0f737082..e02cdaf18ad 100644 --- a/src/Polly/Policy.SyncGenericImplementation.cs +++ b/src/Polly/Policy.SyncGenericImplementation.cs @@ -1,20 +1,21 @@ using System; using System.Threading; -namespace Polly; - -public abstract partial class Policy +namespace Polly { - /// - /// Defines the implementation of a policy for synchronous executions returning . - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// A result of the execution. - protected abstract TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken - ); -} \ No newline at end of file + public abstract partial class Policy + { + /// + /// Defines the implementation of a policy for synchronous executions returning . + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// A result of the execution. + protected abstract TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken + ); + } +} diff --git a/src/Polly/Policy.SyncNonGenericImplementation.cs b/src/Polly/Policy.SyncNonGenericImplementation.cs index da20d0adf15..60a1c46522e 100644 --- a/src/Polly/Policy.SyncNonGenericImplementation.cs +++ b/src/Polly/Policy.SyncNonGenericImplementation.cs @@ -3,27 +3,28 @@ using System.Threading; using Polly.Utilities; -namespace Polly; - -public abstract partial class Policy +namespace Polly { - /// - /// Defines the implementation of a policy for sync executions with no return value. - /// - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - [DebuggerStepThrough] - protected virtual void Implementation(Action action, Context context, CancellationToken cancellationToken) - => Implementation((ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, context, cancellationToken); + public abstract partial class Policy + { + /// + /// Defines the implementation of a policy for sync executions with no return value. + /// + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + [DebuggerStepThrough] + protected virtual void Implementation(Action action, Context context, CancellationToken cancellationToken) + => Implementation((ctx, token) => { action(ctx, token); return EmptyStruct.Instance; }, context, cancellationToken); - /// - /// Defines the implementation of a policy for synchronous executions returning . - /// - /// The type returned by synchronous executions through the implementation. - /// The action passed by calling code to execute through the policy. - /// The policy execution context. - /// A token to signal that execution should be cancelled. - /// A result of the execution. - protected abstract TResult Implementation(Func action, Context context, CancellationToken cancellationToken); -} \ No newline at end of file + /// + /// Defines the implementation of a policy for synchronous executions returning . + /// + /// The type returned by synchronous executions through the implementation. + /// The action passed by calling code to execute through the policy. + /// The policy execution context. + /// A token to signal that execution should be cancelled. + /// A result of the execution. + protected abstract TResult Implementation(Func action, Context context, CancellationToken cancellationToken); + } +} diff --git a/src/Polly/Policy.TResult.ExecuteOverloads.cs b/src/Polly/Policy.TResult.ExecuteOverloads.cs index 99698f4dd89..10505854f9f 100644 --- a/src/Polly/Policy.TResult.ExecuteOverloads.cs +++ b/src/Polly/Policy.TResult.ExecuteOverloads.cs @@ -4,181 +4,182 @@ using System.Threading; -namespace Polly; - -public abstract partial class Policy : ISyncPolicy +namespace Polly { - - #region Execute overloads - - /// - /// Executes the specified action within the policy and returns the Result. - /// - /// The action to perform. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action) - => Execute((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData) - => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// context - /// - /// The value returned by the action - /// - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, Context context) - => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, CancellationToken cancellationToken) - => Execute((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - /// contextData - [DebuggerStepThrough] - public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) - => Execute(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The value returned by the action - [DebuggerStepThrough] - public TResult Execute(Func action, Context context, CancellationToken cancellationToken) + public abstract partial class Policy : ISyncPolicy { - if (context == null) throw new ArgumentNullException(nameof(context)); - - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); - try + #region Execute overloads + + /// + /// Executes the specified action within the policy and returns the Result. + /// + /// The action to perform. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action) + => Execute((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData) + => Execute((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// context + /// + /// The value returned by the action + /// + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, Context context) + => Execute((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, CancellationToken cancellationToken) + => Execute((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + /// contextData + [DebuggerStepThrough] + public TResult Execute(Func action, IDictionary contextData, CancellationToken cancellationToken) + => Execute(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The value returned by the action + [DebuggerStepThrough] + public TResult Execute(Func action, Context context, CancellationToken cancellationToken) { - return Implementation(action, context, cancellationToken); + if (context == null) throw new ArgumentNullException(nameof(context)); + + SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + + try + { + return Implementation(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } - finally + + #endregion + + #region ExecuteAndCapture overloads + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action) + => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) + => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// contextData + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context) + => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result + /// + /// The action to perform. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) + => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Arbitrary data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + /// contextData + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) + => ExecuteAndCapture(action, new Context(contextData), cancellationToken); + + /// + /// Executes the specified action within the policy and returns the captured result. + /// + /// The action to perform. + /// Context data that is passed to the exception policy. + /// The cancellation token. + /// The captured result + [DebuggerStepThrough] + public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) { - RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); - } - } + if (context == null) throw new ArgumentNullException(nameof(context)); - #endregion - - #region ExecuteAndCapture overloads - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action) - => ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData) - => ExecuteAndCapture((ctx, _) => action(ctx), new Context(contextData), DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// contextData - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context) - => ExecuteAndCapture((ctx, _) => action(ctx), context, DefaultCancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result - /// - /// The action to perform. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) - => ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Arbitrary data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - /// contextData - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, IDictionary contextData, CancellationToken cancellationToken) - => ExecuteAndCapture(action, new Context(contextData), cancellationToken); - - /// - /// Executes the specified action within the policy and returns the captured result. - /// - /// The action to perform. - /// Context data that is passed to the exception policy. - /// The cancellation token. - /// The captured result - [DebuggerStepThrough] - public PolicyResult ExecuteAndCapture(Func action, Context context, CancellationToken cancellationToken) - { - if (context == null) throw new ArgumentNullException(nameof(context)); + try + { + var result = Execute(action, context, cancellationToken); - try - { - var result = Execute(action, context, cancellationToken); + if (ResultPredicates.AnyMatch(result)) + { + return PolicyResult.Failure(result, context); + } - if (ResultPredicates.AnyMatch(result)) + return PolicyResult.Successful(result, context); + } + catch (Exception exception) { - return PolicyResult.Failure(result, context); + return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } - - return PolicyResult.Successful(result, context); - } - catch (Exception exception) - { - return PolicyResult.Failure(exception, GetExceptionType(ExceptionPredicates, exception), context); } - } - #endregion -} \ No newline at end of file + #endregion + } +} diff --git a/src/Polly/Policy.TResult.cs b/src/Polly/Policy.TResult.cs index 4d0b5a4cff8..dfe08232912 100644 --- a/src/Polly/Policy.TResult.cs +++ b/src/Polly/Policy.TResult.cs @@ -1,26 +1,27 @@ -namespace Polly; - -/// -/// Transient fault handling policies that can be applied to delegates returning results of type -/// -public abstract partial class Policy : PolicyBase +namespace Polly { /// - /// Constructs a new instance of a derived type with the passed and . + /// Transient fault handling policies that can be applied to delegates returning results of type /// - /// Predicates indicating which exceptions the policy should handle. - /// Predicates indicating which results the policy should handle. - internal Policy(ExceptionPredicates exceptionPredicates, ResultPredicates resultPredicates) - : base(exceptionPredicates, resultPredicates) + public abstract partial class Policy : PolicyBase { - } + /// + /// Constructs a new instance of a derived type with the passed and . + /// + /// Predicates indicating which exceptions the policy should handle. + /// Predicates indicating which results the policy should handle. + internal Policy(ExceptionPredicates exceptionPredicates, ResultPredicates resultPredicates) + : base(exceptionPredicates, resultPredicates) + { + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A indicating which exceptions and results the policy should handle. - protected Policy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A indicating which exceptions and results the policy should handle. + protected Policy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { + } } -} \ No newline at end of file +} diff --git a/src/Polly/Policy.cs b/src/Polly/Policy.cs index adf7c7c8707..3c4f18d1a28 100644 --- a/src/Polly/Policy.cs +++ b/src/Polly/Policy.cs @@ -1,25 +1,26 @@ -namespace Polly; - -/// -/// Transient exception handling policies that can be applied to synchronous delegates -/// -public abstract partial class Policy : PolicyBase +namespace Polly { /// - /// Constructs a new instance of a derived type with the passed . + /// Transient exception handling policies that can be applied to synchronous delegates /// - /// Predicates indicating which exceptions the policy should handle. - internal Policy(ExceptionPredicates exceptionPredicates) - : base(exceptionPredicates) + public abstract partial class Policy : PolicyBase { - } + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// Predicates indicating which exceptions the policy should handle. + internal Policy(ExceptionPredicates exceptionPredicates) + : base(exceptionPredicates) + { + } - /// - /// Constructs a new instance of a derived type with the passed . - /// - /// A specifying which exceptions the policy should handle. - protected Policy(PolicyBuilder policyBuilder = null) - : base(policyBuilder) - { + /// + /// Constructs a new instance of a derived type with the passed . + /// + /// A specifying which exceptions the policy should handle. + protected Policy(PolicyBuilder policyBuilder = null) + : base(policyBuilder) + { + } } } \ No newline at end of file diff --git a/src/Polly/PolicyBase.ContextAndKeys.cs b/src/Polly/PolicyBase.ContextAndKeys.cs index 28a971e7c53..2f584230ad4 100644 --- a/src/Polly/PolicyBase.ContextAndKeys.cs +++ b/src/Polly/PolicyBase.ContextAndKeys.cs @@ -1,45 +1,46 @@ using System; using Polly.Utilities; -namespace Polly; - -public abstract partial class PolicyBase +namespace Polly { - /// - /// A key intended to be unique to each instance. - /// - protected string policyKeyInternal; + public abstract partial class PolicyBase + { + /// + /// A key intended to be unique to each instance. + /// + protected string policyKeyInternal; - /// - /// A key intended to be unique to each instance, which is passed with executions as the property. - /// - public string PolicyKey => policyKeyInternal ?? (policyKeyInternal = GetType().Name + "-" + KeyHelper.GuidPart()); + /// + /// A key intended to be unique to each instance, which is passed with executions as the property. + /// + public string PolicyKey => policyKeyInternal ?? (policyKeyInternal = GetType().Name + "-" + KeyHelper.GuidPart()); - internal static ArgumentException PolicyKeyMustBeImmutableException => new ArgumentException("PolicyKey cannot be changed once set; or (when using the default value after the PolicyKey property has been accessed.", "policyKey"); + internal static ArgumentException PolicyKeyMustBeImmutableException => new ArgumentException("PolicyKey cannot be changed once set; or (when using the default value after the PolicyKey property has been accessed.", "policyKey"); - /// - /// Updates the execution with context from the executing policy. - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal virtual void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) // priorPolicyWrapKey and priorPolicyKey could be handled as a (string, string) System.ValueTuple return type instead of out parameters, when our minimum supported target supports this. - { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + /// + /// Updates the execution with context from the executing policy. + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal virtual void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) // priorPolicyWrapKey and priorPolicyKey could be handled as a (string, string) System.ValueTuple return type instead of out parameters, when our minimum supported target supports this. + { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - executionContext.PolicyKey = PolicyKey; - } + executionContext.PolicyKey = PolicyKey; + } - /// - /// Restores the supplied keys to the execution . - /// - /// The execution . - /// The prior to execution through this policy. - /// The prior to execution through this policy. - internal void RestorePolicyContext(Context executionContext, string priorPolicyWrapKey, string priorPolicyKey) - { - executionContext.PolicyWrapKey = priorPolicyWrapKey; - executionContext.PolicyKey = priorPolicyKey; + /// + /// Restores the supplied keys to the execution . + /// + /// The execution . + /// The prior to execution through this policy. + /// The prior to execution through this policy. + internal void RestorePolicyContext(Context executionContext, string priorPolicyWrapKey, string priorPolicyKey) + { + executionContext.PolicyWrapKey = priorPolicyWrapKey; + executionContext.PolicyKey = priorPolicyKey; + } } } \ No newline at end of file diff --git a/src/Polly/PolicyBase.cs b/src/Polly/PolicyBase.cs index e71ecc10d7c..708d7ba9169 100644 --- a/src/Polly/PolicyBase.cs +++ b/src/Polly/PolicyBase.cs @@ -1,81 +1,82 @@ using System; using System.Threading; -namespace Polly; - -/// -/// Implements elements common to both non-generic and generic policies, and sync and async policies. -/// -public abstract partial class PolicyBase +namespace Polly { /// - /// Predicates indicating which exceptions the policy handles. + /// Implements elements common to both non-generic and generic policies, and sync and async policies. /// - protected internal ExceptionPredicates ExceptionPredicates { get; } + public abstract partial class PolicyBase + { + /// + /// Predicates indicating which exceptions the policy handles. + /// + protected internal ExceptionPredicates ExceptionPredicates { get; } - /// - /// Defines a CancellationToken to use, when none is supplied. - /// - internal readonly CancellationToken DefaultCancellationToken = CancellationToken.None; + /// + /// Defines a CancellationToken to use, when none is supplied. + /// + internal readonly CancellationToken DefaultCancellationToken = CancellationToken.None; - /// - /// Defines a value to use for continueOnCaptureContext, when none is supplied. - /// - internal const bool DefaultContinueOnCapturedContext = false; + /// + /// Defines a value to use for continueOnCaptureContext, when none is supplied. + /// + internal const bool DefaultContinueOnCapturedContext = false; - internal static ExceptionType GetExceptionType(ExceptionPredicates exceptionPredicates, Exception exception) - { - var isExceptionTypeHandledByThisPolicy = exceptionPredicates.FirstMatchOrDefault(exception) != null; + internal static ExceptionType GetExceptionType(ExceptionPredicates exceptionPredicates, Exception exception) + { + var isExceptionTypeHandledByThisPolicy = exceptionPredicates.FirstMatchOrDefault(exception) != null; - return isExceptionTypeHandledByThisPolicy - ? ExceptionType.HandledByThisPolicy - : ExceptionType.Unhandled; - } + return isExceptionTypeHandledByThisPolicy + ? ExceptionType.HandledByThisPolicy + : ExceptionType.Unhandled; + } - /// - /// Constructs a new instance of a derived type of with the passed . - /// - /// Predicates indicating which exceptions the policy should handle. - internal PolicyBase(ExceptionPredicates exceptionPredicates) - => ExceptionPredicates = exceptionPredicates ?? ExceptionPredicates.None; + /// + /// Constructs a new instance of a derived type of with the passed . + /// + /// Predicates indicating which exceptions the policy should handle. + internal PolicyBase(ExceptionPredicates exceptionPredicates) + => ExceptionPredicates = exceptionPredicates ?? ExceptionPredicates.None; - /// - /// Constructs a new instance of a derived type of with the passed . - /// - /// A indicating which exceptions the policy should handle. - protected PolicyBase(PolicyBuilder policyBuilder) - : this(policyBuilder?.ExceptionPredicates) - { + /// + /// Constructs a new instance of a derived type of with the passed . + /// + /// A indicating which exceptions the policy should handle. + protected PolicyBase(PolicyBuilder policyBuilder) + : this(policyBuilder?.ExceptionPredicates) + { + } } -} -/// -/// Implements elements common to sync and async generic policies. -/// -public abstract class PolicyBase : PolicyBase -{ /// - /// Predicates indicating which results the policy handles. + /// Implements elements common to sync and async generic policies. /// - protected internal ResultPredicates ResultPredicates { get; } + public abstract class PolicyBase : PolicyBase + { + /// + /// Predicates indicating which results the policy handles. + /// + protected internal ResultPredicates ResultPredicates { get; } - /// - /// Constructs a new instance of a derived type of . - /// - /// Predicates indicating which exceptions the policy should handle. - /// Predicates indicating which results the policy should handle. - internal PolicyBase( - ExceptionPredicates exceptionPredicates, - ResultPredicates resultPredicates) + /// + /// Constructs a new instance of a derived type of . + /// + /// Predicates indicating which exceptions the policy should handle. + /// Predicates indicating which results the policy should handle. + internal PolicyBase( + ExceptionPredicates exceptionPredicates, + ResultPredicates resultPredicates) : base(exceptionPredicates) - => ResultPredicates = resultPredicates ?? ResultPredicates.None; + => ResultPredicates = resultPredicates ?? ResultPredicates.None; - /// - /// Constructs a new instance of a derived type of with the passed . - /// - /// A indicating which exceptions the policy should handle. - protected PolicyBase(PolicyBuilder policyBuilder) - : this(policyBuilder?.ExceptionPredicates, policyBuilder?.ResultPredicates) - { + /// + /// Constructs a new instance of a derived type of with the passed . + /// + /// A indicating which exceptions the policy should handle. + protected PolicyBase(PolicyBuilder policyBuilder) + : this(policyBuilder?.ExceptionPredicates, policyBuilder?.ResultPredicates) + { + } } -} \ No newline at end of file +} diff --git a/src/Polly/PolicyBuilder.OrSyntax.cs b/src/Polly/PolicyBuilder.OrSyntax.cs index 4f47d037c20..36fe5c83792 100644 --- a/src/Polly/PolicyBuilder.OrSyntax.cs +++ b/src/Polly/PolicyBuilder.OrSyntax.cs @@ -1,181 +1,182 @@ using System; -namespace Polly; - -public partial class PolicyBuilder +namespace Polly { - #region Add exception predicates to exception-filtering policy - - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or() where TException : Exception - { - ExceptionPredicates.Add(exception => exception is TException ? exception : null); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception - { - ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner() where TException : Exception - { - ExceptionPredicates.Add((HandleInner(ex => ex is TException))); - return this; - } - - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception + public partial class PolicyBuilder { - ExceptionPredicates.Add(HandleInner(exception => exception is TException texception && exceptionPredicate(texception))); - return this; - } + #region Add exception predicates to exception-filtering policy + + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or() where TException : Exception + { + ExceptionPredicates.Add(exception => exception is TException ? exception : null); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner() where TException : Exception + { + ExceptionPredicates.Add((HandleInner(ex => ex is TException))); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(HandleInner(exception => exception is TException texception && exceptionPredicate(texception))); + return this; + } - internal static ExceptionPredicate HandleInner(Func predicate) - { - return exception => + internal static ExceptionPredicate HandleInner(Func predicate) { - if (exception is AggregateException aggregateException) + return exception => { - //search all inner exceptions wrapped inside the AggregateException recursively - foreach (var innerException in aggregateException.Flatten().InnerExceptions) + if (exception is AggregateException aggregateException) { - var matchedInAggregate = HandleInnerNested(predicate, innerException); - if (matchedInAggregate != null) - return matchedInAggregate; + //search all inner exceptions wrapped inside the AggregateException recursively + foreach (var innerException in aggregateException.Flatten().InnerExceptions) + { + var matchedInAggregate = HandleInnerNested(predicate, innerException); + if (matchedInAggregate != null) + return matchedInAggregate; + } } - } - - return HandleInnerNested(predicate, exception); - }; - } - - private static Exception HandleInnerNested(Func predicate, Exception current) - { - if (current == null) return null; - if (predicate(current)) return current; - return HandleInnerNested(predicate, current.InnerException); - } - #endregion - - #region Add result predicates to exception-filtering policy - - /// - /// Specifies the type of result that this policy can handle with additional filters on the result. - /// - /// The type of return values this policy will handle. - /// The predicate to filter the results this policy will handle. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(Func resultPredicate) - => new PolicyBuilder(ExceptionPredicates).OrResult(resultPredicate); - - /// - /// Specifies a result value which the policy will handle. - /// - /// The type of return values this policy will handle. - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(TResult result) - => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); - - #endregion -} -public partial class PolicyBuilder -{ + return HandleInnerNested(predicate, exception); + }; + } - #region Add result predicates to result-filtering policy - - /// - /// Specifies the type of result that this policy can handle with additional filters on the result. - /// - /// The predicate to filter the results this policy will handle. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(Func resultPredicate) - { - ResultPredicate predicate = result => resultPredicate(result); - ResultPredicates.Add(predicate); - return this; + private static Exception HandleInnerNested(Func predicate, Exception current) + { + if (current == null) return null; + if (predicate(current)) return current; + return HandleInnerNested(predicate, current.InnerException); + } + + #endregion + + #region Add result predicates to exception-filtering policy + + /// + /// Specifies the type of result that this policy can handle with additional filters on the result. + /// + /// The type of return values this policy will handle. + /// The predicate to filter the results this policy will handle. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(Func resultPredicate) + => new PolicyBuilder(ExceptionPredicates).OrResult(resultPredicate); + + /// + /// Specifies a result value which the policy will handle. + /// + /// The type of return values this policy will handle. + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(TResult result) + => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); + + #endregion } - - /// - /// Specifies a result value which the policy will handle. - /// - /// The TResult value this policy will handle. - /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. - /// The PolicyBuilder instance. - public PolicyBuilder OrResult(TResult result) - => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); - - #endregion - - #region Add exception predicates to result-filtering policy - - /// - /// Specifies the type of exception that this policy can handle. - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or() where TException : Exception + public partial class PolicyBuilder { - ExceptionPredicates.Add(exception => exception is TException ? exception : null); - return this; - } - /// - /// Specifies the type of exception that this policy can handle with additional filters on this exception type. - /// - /// The type of the exception. - /// The exception predicate to filter the type of exception this policy can handle. - /// The PolicyBuilder instance. - public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception - { - ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); - return this; - } + #region Add result predicates to result-filtering policy - /// - /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner() where TException : Exception - { - ExceptionPredicates.Add((PolicyBuilder.HandleInner(ex => ex is TException))); - return this; - } + /// + /// Specifies the type of result that this policy can handle with additional filters on the result. + /// + /// The predicate to filter the results this policy will handle. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(Func resultPredicate) + { + ResultPredicate predicate = result => resultPredicate(result); + ResultPredicates.Add(predicate); + return this; + } + + /// + /// Specifies a result value which the policy will handle. + /// + /// The TResult value this policy will handle. + /// This policy filter matches the value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate. + /// The PolicyBuilder instance. + public PolicyBuilder OrResult(TResult result) + => OrResult(r => (r != null && r.Equals(result)) || (r == null && result == null)); + + #endregion + + #region Add exception predicates to result-filtering policy + + /// + /// Specifies the type of exception that this policy can handle. + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or() where TException : Exception + { + ExceptionPredicates.Add(exception => exception is TException ? exception : null); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle with additional filters on this exception type. + /// + /// The type of the exception. + /// The exception predicate to filter the type of exception this policy can handle. + /// The PolicyBuilder instance. + public PolicyBuilder Or(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(exception => exception is TException texception && exceptionPredicate(texception) ? exception : null); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner() where TException : Exception + { + ExceptionPredicates.Add((PolicyBuilder.HandleInner(ex => ex is TException))); + return this; + } + + /// + /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . + /// + /// The type of the exception to handle. + /// The PolicyBuilder instance, for fluent chaining. + public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception + { + ExceptionPredicates.Add(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); + return this; + } - /// - /// Specifies the type of exception that this policy can handle, with additional filters on this exception type, if found as an InnerException of a regular , or at any level of nesting within an . - /// - /// The type of the exception to handle. - /// The PolicyBuilder instance, for fluent chaining. - public PolicyBuilder OrInner(Func exceptionPredicate) where TException : Exception - { - ExceptionPredicates.Add(PolicyBuilder.HandleInner(ex => ex is TException texception && exceptionPredicate(texception))); - return this; + #endregion } - - #endregion -} \ No newline at end of file +} diff --git a/src/Polly/PolicyBuilder.cs b/src/Polly/PolicyBuilder.cs index 9586521f798..a00802069d5 100644 --- a/src/Polly/PolicyBuilder.cs +++ b/src/Polly/PolicyBuilder.cs @@ -1,147 +1,148 @@ using System; using System.ComponentModel; -namespace Polly; - -/// -/// Builder class that holds the list of current exception predicates. -/// -public sealed partial class PolicyBuilder +namespace Polly { - internal PolicyBuilder(ExceptionPredicate exceptionPredicate) - { - ExceptionPredicates = new ExceptionPredicates(); - ExceptionPredicates.Add(exceptionPredicate); - } - - /// - /// Predicates specifying exceptions that the policy is being configured to handle. - /// - internal ExceptionPredicates ExceptionPredicates { get; } - - #region Hide object members - /// - /// Returns a that represents this instance. + /// Builder class that holds the list of current exception predicates. /// - /// - /// A that represents this instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string ToString() + public sealed partial class PolicyBuilder { - return base.ToString(); + internal PolicyBuilder(ExceptionPredicate exceptionPredicate) + { + ExceptionPredicates = new ExceptionPredicates(); + ExceptionPredicates.Add(exceptionPredicate); + } + + /// + /// Predicates specifying exceptions that the policy is being configured to handle. + /// + internal ExceptionPredicates ExceptionPredicates { get; } + + #region Hide object members + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() + { + return base.ToString(); + } + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + /// Gets the of the current instance. + /// + /// + /// The instance that represents the exact runtime type of the current instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + + #endregion } /// - /// Determines whether the specified is equal to this instance. + /// Builder class that holds the list of current execution predicates filtering TResult result values. /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) - { - return base.Equals(obj); - } - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() - { - return base.GetHashCode(); - } - - /// - /// Gets the of the current instance. - /// - /// - /// The instance that represents the exact runtime type of the current instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public new Type GetType() - { - return base.GetType(); - } - - #endregion -} - -/// -/// Builder class that holds the list of current execution predicates filtering TResult result values. -/// -public sealed partial class PolicyBuilder -{ - private PolicyBuilder() + public sealed partial class PolicyBuilder { - ExceptionPredicates = new ExceptionPredicates(); - ResultPredicates = new ResultPredicates(); + private PolicyBuilder() + { + ExceptionPredicates = new ExceptionPredicates(); + ResultPredicates = new ResultPredicates(); + } + + internal PolicyBuilder(Func resultPredicate) : this() + => OrResult(resultPredicate); + + internal PolicyBuilder(ExceptionPredicate predicate) : this() + => ExceptionPredicates.Add(predicate); + + internal PolicyBuilder(ExceptionPredicates exceptionPredicates) + : this() + => ExceptionPredicates = exceptionPredicates; + + /// + /// Predicates specifying exceptions that the policy is being configured to handle. + /// + internal ExceptionPredicates ExceptionPredicates { get; } + + /// + /// Predicates specifying results that the policy is being configured to handle. + /// + internal ResultPredicates ResultPredicates { get; } + + #region Hide object members + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() => base.ToString(); + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => base.Equals(obj); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + /// Gets the of the current instance. + /// + /// + /// The instance that represents the exact runtime type of the current instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() => base.GetType(); + + #endregion } - - internal PolicyBuilder(Func resultPredicate) : this() - => OrResult(resultPredicate); - - internal PolicyBuilder(ExceptionPredicate predicate) : this() - => ExceptionPredicates.Add(predicate); - - internal PolicyBuilder(ExceptionPredicates exceptionPredicates) - : this() - => ExceptionPredicates = exceptionPredicates; - - /// - /// Predicates specifying exceptions that the policy is being configured to handle. - /// - internal ExceptionPredicates ExceptionPredicates { get; } - - /// - /// Predicates specifying results that the policy is being configured to handle. - /// - internal ResultPredicates ResultPredicates { get; } - - #region Hide object members - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string ToString() => base.ToString(); - - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => base.Equals(obj); - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); - - /// - /// Gets the of the current instance. - /// - /// - /// The instance that represents the exact runtime type of the current instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public new Type GetType() => base.GetType(); - - #endregion } \ No newline at end of file diff --git a/src/Polly/PolicyResult.cs b/src/Polly/PolicyResult.cs index c93c2db687c..81f5c2c08bb 100644 --- a/src/Polly/PolicyResult.cs +++ b/src/Polly/PolicyResult.cs @@ -1,208 +1,209 @@ using System; -namespace Polly; - -/// -/// The captured result of executing a policy -/// -public class PolicyResult +namespace Polly { - internal PolicyResult(OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) - { - Outcome = outcome; - FinalException = finalException; - ExceptionType = exceptionType; - Context = context; - } - - /// - /// The outcome of executing the policy - /// - public OutcomeType Outcome { get; } - - /// - /// The final exception captured. Will be null if policy executed successfully - /// - public Exception FinalException { get; } - - /// - /// The exception type of the final exception captured. Will be null if policy executed successfully - /// - public ExceptionType? ExceptionType { get; } - - /// - /// The context for this execution. - /// - public Context Context { get; } - - /// - /// Builds a representing a successful execution through the policy. - /// - /// The policy execution context - /// - /// A representing a successful execution through the policy. - /// - public static PolicyResult Successful(Context context) - => new PolicyResult(OutcomeType.Successful, null, null, context); - /// - /// Builds a representing a failed execution through the policy. /> + /// The captured result of executing a policy /// - /// The exception - /// The exception type - /// The policy execution context - /// - /// A representing a failed execution through the policy. - /// - public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) - => new PolicyResult(OutcomeType.Failure, exception, exceptionType, context); -} - -/// -/// The captured result of executing a policy -/// -public class PolicyResult -{ - internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) - : this(result, outcome, finalException, exceptionType, default, null, context) + public class PolicyResult { - + internal PolicyResult(OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) + { + Outcome = outcome; + FinalException = finalException; + ExceptionType = exceptionType; + Context = context; + } + + /// + /// The outcome of executing the policy + /// + public OutcomeType Outcome { get; } + + /// + /// The final exception captured. Will be null if policy executed successfully + /// + public Exception FinalException { get; } + + /// + /// The exception type of the final exception captured. Will be null if policy executed successfully + /// + public ExceptionType? ExceptionType { get; } + + /// + /// The context for this execution. + /// + public Context Context { get; } + + /// + /// Builds a representing a successful execution through the policy. + /// + /// The policy execution context + /// + /// A representing a successful execution through the policy. + /// + public static PolicyResult Successful(Context context) + => new PolicyResult(OutcomeType.Successful, null, null, context); + + /// + /// Builds a representing a failed execution through the policy. /> + /// + /// The exception + /// The exception type + /// The policy execution context + /// + /// A representing a failed execution through the policy. + /// + public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) + => new PolicyResult(OutcomeType.Failure, exception, exceptionType, context); } - internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, TResult finalHandledResult, FaultType? faultType, Context context) - { - Result = result; - Outcome = outcome; - FinalException = finalException; - ExceptionType = exceptionType; - FinalHandledResult = finalHandledResult; - FaultType = faultType; - Context = context; - } - - /// - /// The outcome of executing the policy - /// - public OutcomeType Outcome { get; } - - /// - /// The final exception captured. Will be null if policy executed without exception. - /// - public Exception FinalException { get; } - - /// - /// The exception type of the final exception captured. Will be null if policy executed successfully - /// - public ExceptionType? ExceptionType { get; } - - /// - /// The result of executing the policy. Will be default(TResult) if the policy failed - /// - public TResult Result { get; } - /// - /// The final handled result captured. Will be default(TResult) if the policy executed successfully, or terminated with an exception. + /// The captured result of executing a policy /// - public TResult FinalHandledResult { get; } - - /// - /// The fault type of the final fault captured. Will be null if policy executed successfully - /// - public FaultType? FaultType { get; } - - /// - /// The context for this execution. - /// - public Context Context { get; } - - /// - /// Builds a representing a successful execution through the policy. - /// - /// The result returned by execution through the policy - /// The policy execution context - /// - /// A representing a successful execution through the policy. - /// - public static PolicyResult Successful(TResult result, Context context) - => new PolicyResult(result, OutcomeType.Successful, null, null, context); - - /// - /// Builds a representing a failed execution through the policy. - /// - /// The exception - /// The exception type - /// The policy execution context - /// - /// A representing a failed execution through the policy. - /// - public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) - => new PolicyResult(default, OutcomeType.Failure, exception, exceptionType, default, - exceptionType == Polly.ExceptionType.HandledByThisPolicy + public class PolicyResult + { + internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, Context context) + : this(result, outcome, finalException, exceptionType, default, null, context) + { + + } + + internal PolicyResult(TResult result, OutcomeType outcome, Exception finalException, ExceptionType? exceptionType, TResult finalHandledResult, FaultType? faultType, Context context) + { + Result = result; + Outcome = outcome; + FinalException = finalException; + ExceptionType = exceptionType; + FinalHandledResult = finalHandledResult; + FaultType = faultType; + Context = context; + } + + /// + /// The outcome of executing the policy + /// + public OutcomeType Outcome { get; } + + /// + /// The final exception captured. Will be null if policy executed without exception. + /// + public Exception FinalException { get; } + + /// + /// The exception type of the final exception captured. Will be null if policy executed successfully + /// + public ExceptionType? ExceptionType { get; } + + /// + /// The result of executing the policy. Will be default(TResult) if the policy failed + /// + public TResult Result { get; } + + /// + /// The final handled result captured. Will be default(TResult) if the policy executed successfully, or terminated with an exception. + /// + public TResult FinalHandledResult { get; } + + /// + /// The fault type of the final fault captured. Will be null if policy executed successfully + /// + public FaultType? FaultType { get; } + + /// + /// The context for this execution. + /// + public Context Context { get; } + + /// + /// Builds a representing a successful execution through the policy. + /// + /// The result returned by execution through the policy + /// The policy execution context + /// + /// A representing a successful execution through the policy. + /// + public static PolicyResult Successful(TResult result, Context context) + => new PolicyResult(result, OutcomeType.Successful, null, null, context); + + /// + /// Builds a representing a failed execution through the policy. + /// + /// The exception + /// The exception type + /// The policy execution context + /// + /// A representing a failed execution through the policy. + /// + public static PolicyResult Failure(Exception exception, ExceptionType exceptionType, Context context) + => new PolicyResult(default, OutcomeType.Failure, exception, exceptionType, default, + exceptionType == Polly.ExceptionType.HandledByThisPolicy ? Polly.FaultType.ExceptionHandledByThisPolicy : Polly.FaultType.UnhandledException, - context); - - /// - /// Builds a representing a failed execution through the policy. - /// - /// The result returned by execution through the policy, which was treated as a handled failure - /// The policy execution context - /// - /// A representing a failed execution through the policy. - /// - public static PolicyResult Failure(TResult handledResult, Context context) - => new PolicyResult(default, OutcomeType.Failure, null, null, handledResult, Polly.FaultType.ResultHandledByThisPolicy, context); -} - -/// -/// Represents the outcome of executing a policy -/// -public enum OutcomeType -{ - /// - /// Indicates that the policy ultimately executed successfully - /// - Successful, - - /// - /// Indicates that the policy ultimately failed - /// - Failure -}; - -/// -/// Represents the type of exception resulting from a failed policy -/// -public enum ExceptionType -{ - /// - /// An exception type that has been defined to be handled by this policy - /// - HandledByThisPolicy, + context); + + /// + /// Builds a representing a failed execution through the policy. + /// + /// The result returned by execution through the policy, which was treated as a handled failure + /// The policy execution context + /// + /// A representing a failed execution through the policy. + /// + public static PolicyResult Failure(TResult handledResult, Context context) + => new PolicyResult(default, OutcomeType.Failure, null, null, handledResult, Polly.FaultType.ResultHandledByThisPolicy, context); + } /// - /// An exception type that has been not been defined to be handled by this policy + /// Represents the outcome of executing a policy /// - Unhandled -} + public enum OutcomeType + { + /// + /// Indicates that the policy ultimately executed successfully + /// + Successful, -/// -/// Represents the type of outcome from a failed policy -/// -public enum FaultType -{ - /// - /// An exception type that has been defined to be handled by this policy - /// - ExceptionHandledByThisPolicy, + /// + /// Indicates that the policy ultimately failed + /// + Failure + }; /// - /// An exception type that has been not been defined to be handled by this policy + /// Represents the type of exception resulting from a failed policy /// - UnhandledException, + public enum ExceptionType + { + /// + /// An exception type that has been defined to be handled by this policy + /// + HandledByThisPolicy, + + /// + /// An exception type that has been not been defined to be handled by this policy + /// + Unhandled + } /// - /// A result value that has been defined to be handled by this policy + /// Represents the type of outcome from a failed policy /// - ResultHandledByThisPolicy + public enum FaultType + { + /// + /// An exception type that has been defined to be handled by this policy + /// + ExceptionHandledByThisPolicy, + + /// + /// An exception type that has been not been defined to be handled by this policy + /// + UnhandledException, + + /// + /// A result value that has been defined to be handled by this policy + /// + ResultHandledByThisPolicy + } } \ No newline at end of file diff --git a/src/Polly/RateLimit/AsyncRateLimitEngine.cs b/src/Polly/RateLimit/AsyncRateLimitEngine.cs index 2ade2ef4e64..fc115226407 100644 --- a/src/Polly/RateLimit/AsyncRateLimitEngine.cs +++ b/src/Polly/RateLimit/AsyncRateLimitEngine.cs @@ -2,31 +2,32 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.RateLimit; - -internal static class AsyncRateLimitEngine +namespace Polly.RateLimit { - internal static async Task ImplementationAsync( - IRateLimiter rateLimiter, - Func retryAfterFactory, - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext - ) + internal static class AsyncRateLimitEngine { - (var permit, var retryAfter) = rateLimiter.PermitExecution(); - - if (permit) + internal static async Task ImplementationAsync( + IRateLimiter rateLimiter, + Func retryAfterFactory, + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext + ) { - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + (var permit, var retryAfter) = rateLimiter.PermitExecution(); - if (retryAfterFactory != null) - { - return retryAfterFactory(retryAfter, context); - } + if (permit) + { + return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } + + if (retryAfterFactory != null) + { + return retryAfterFactory(retryAfter, context); + } - throw new RateLimitRejectedException(retryAfter); + throw new RateLimitRejectedException(retryAfter); + } } -} \ No newline at end of file +} diff --git a/src/Polly/RateLimit/AsyncRateLimitPolicy.cs b/src/Polly/RateLimit/AsyncRateLimitPolicy.cs index b6a58ae6171..261a9ea3dee 100644 --- a/src/Polly/RateLimit/AsyncRateLimitPolicy.cs +++ b/src/Polly/RateLimit/AsyncRateLimitPolicy.cs @@ -3,46 +3,47 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.RateLimit; - -/// -/// A rate-limit policy that can be applied to asynchronous delegates. -/// -public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy +namespace Polly.RateLimit { - private readonly IRateLimiter _rateLimiter; - - internal AsyncRateLimitPolicy(IRateLimiter rateLimiter) + /// + /// A rate-limit policy that can be applied to asynchronous delegates. + /// + public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - } + private readonly IRateLimiter _rateLimiter; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, null, action, context, cancellationToken, continueOnCapturedContext); -} + internal AsyncRateLimitPolicy(IRateLimiter rateLimiter) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); + } -/// -/// A rate-limit policy that can be applied to asynchronous delegates returning a value of type . -/// -public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy -{ - private readonly IRateLimiter _rateLimiter; - private readonly Func _retryAfterFactory; + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, null, action, context, cancellationToken, continueOnCapturedContext); + } - internal AsyncRateLimitPolicy( - IRateLimiter rateLimiter, - Func retryAfterFactory) + /// + /// A rate-limit policy that can be applied to asynchronous delegates returning a value of type . + /// + public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - _retryAfterFactory = retryAfterFactory; - } + private readonly IRateLimiter _rateLimiter; + private readonly Func _retryAfterFactory; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, _retryAfterFactory, action, context, cancellationToken, continueOnCapturedContext); + internal AsyncRateLimitPolicy( + IRateLimiter rateLimiter, + Func retryAfterFactory) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); + _retryAfterFactory = retryAfterFactory; + } + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, _retryAfterFactory, action, context, cancellationToken, continueOnCapturedContext); + } } \ No newline at end of file diff --git a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs index ef414a5ce6a..62bb3408195 100644 --- a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs @@ -1,49 +1,50 @@ using System; using Polly.RateLimit; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan) + public partial class Policy { - return RateLimitAsync(numberOfExecutions, perTimeSpan, 1); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, 1); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new AsyncRateLimitPolicy(rateLimiter); + return new AsyncRateLimitPolicy(rateLimiter); + } } -} \ No newline at end of file +} diff --git a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs index f18086f62ae..64ba73141cf 100644 --- a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs @@ -1,89 +1,90 @@ using System; using Polly.RateLimit; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan) + public partial class Policy { - return RateLimitAsync(numberOfExecutions, perTimeSpan, null); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - Func retryAfterFactory) - { - return RateLimitAsync(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + Func retryAfterFactory) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - return RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, null); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + return RateLimitAsync(numberOfExecutions, perTimeSpan, maxBurst, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given, - /// with a maximum burst size of - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static AsyncRateLimitPolicy RateLimitAsync( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst, - Func retryAfterFactory) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given, + /// with a maximum burst size of + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static AsyncRateLimitPolicy RateLimitAsync( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst, + Func retryAfterFactory) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new AsyncRateLimitPolicy(rateLimiter, retryAfterFactory); + return new AsyncRateLimitPolicy(rateLimiter, retryAfterFactory); + } } -} \ No newline at end of file +} diff --git a/src/Polly/RateLimit/IRateLimitPolicy.cs b/src/Polly/RateLimit/IRateLimitPolicy.cs index 1defff8faaf..88c967e0c3c 100644 --- a/src/Polly/RateLimit/IRateLimitPolicy.cs +++ b/src/Polly/RateLimit/IRateLimitPolicy.cs @@ -1,15 +1,16 @@ -namespace Polly.RateLimit; - -/// -/// Defines properties and methods common to all RateLimit policies. -/// -public interface IRateLimitPolicy : IsPolicy +namespace Polly.RateLimit { -} + /// + /// Defines properties and methods common to all RateLimit policies. + /// + public interface IRateLimitPolicy : IsPolicy + { + } -/// -/// Defines properties and methods common to all RateLimit policies generic-typed for executions returning results of type . -/// -public interface IRateLimitPolicy : IRateLimitPolicy -{ -} \ No newline at end of file + /// + /// Defines properties and methods common to all RateLimit policies generic-typed for executions returning results of type . + /// + public interface IRateLimitPolicy : IRateLimitPolicy + { + } +} diff --git a/src/Polly/RateLimit/IRateLimiter.cs b/src/Polly/RateLimit/IRateLimiter.cs index 5fee277f288..79e724f3acb 100644 --- a/src/Polly/RateLimit/IRateLimiter.cs +++ b/src/Polly/RateLimit/IRateLimiter.cs @@ -1,15 +1,16 @@ using System; -namespace Polly.RateLimit; - -/// -/// Defines methods to be provided by a rate-limiter used in a Polly -/// -internal interface IRateLimiter +namespace Polly.RateLimit { /// - /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. - /// Calling this method consumes an execution permit if one is available: a caller receiving a return value true should make an execution. + /// Defines methods to be provided by a rate-limiter used in a Polly /// - (bool permitExecution, TimeSpan retryAfter) PermitExecution(); -} \ No newline at end of file + internal interface IRateLimiter + { + /// + /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. + /// Calling this method consumes an execution permit if one is available: a caller receiving a return value true should make an execution. + /// + (bool permitExecution, TimeSpan retryAfter) PermitExecution(); + } +} diff --git a/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs b/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs index b6fe361ec2a..e1e0ec468ac 100644 --- a/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs +++ b/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs @@ -2,114 +2,115 @@ using System.Threading; using Polly.Utilities; -namespace Polly.RateLimit; - -/// -/// A lock-free token-bucket rate-limiter for a Polly . -/// -internal sealed class LockFreeTokenBucketRateLimiter : IRateLimiter +namespace Polly.RateLimit { - private readonly long addTokenTickInterval; - private readonly long bucketCapacity; - - private long currentTokens; - - private long addNextTokenAtTicks; - -#if !NETSTANDARD2_0 - private SpinWait spinner = new(); -#endif - /// - /// Creates an instance of + /// A lock-free token-bucket rate-limiter for a Polly . /// - /// How often one execution is permitted. - /// The capacity of the token bucket. - /// This equates to the maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// - public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity) + internal sealed class LockFreeTokenBucketRateLimiter : IRateLimiter { - if (onePer <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(onePer), onePer, $"The {nameof(LockFreeTokenBucketRateLimiter)} must specify a positive TimeSpan for how often an execution is permitted."); - if (bucketCapacity <= 0) throw new ArgumentOutOfRangeException(nameof(bucketCapacity), bucketCapacity, $"{nameof(bucketCapacity)} must be greater than or equal to 1."); - - addTokenTickInterval = onePer.Ticks; - this.bucketCapacity = bucketCapacity; + private readonly long addTokenTickInterval; + private readonly long bucketCapacity; - currentTokens = bucketCapacity; - addNextTokenAtTicks = SystemClock.DateTimeOffsetUtcNow().Ticks + addTokenTickInterval; - } - - /// - /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. - /// - public (bool permitExecution, TimeSpan retryAfter) PermitExecution() - { - while (true) - { - // Try to get a token. - var tokensAfterGrabOne = Interlocked.Decrement(ref currentTokens); + private long currentTokens; - if (tokensAfterGrabOne >= 0) - { - // We got a token: permit execution! - return (true, TimeSpan.Zero); - } + private long addNextTokenAtTicks; - // No tokens! We're rate-limited - unless we can refill the bucket. - var now = SystemClock.DateTimeOffsetUtcNow().Ticks; - var currentAddNextTokenAtTicks = Interlocked.Read(ref addNextTokenAtTicks); - var ticksTillAddNextToken = currentAddNextTokenAtTicks - now; - - if (ticksTillAddNextToken > 0) - { - // Not time to add tokens yet: we're rate-limited! - return (false, TimeSpan.FromTicks(ticksTillAddNextToken)); - } - - // Time to add tokens to the bucket! - - // We definitely need to add one token. In fact, if we haven't hit this bit of code for a while, we might be due to add a bunch of tokens. - var tokensMissedAdding = - // Passing addNextTokenAtTicks merits one token - 1 + - // And any whole token tick intervals further each merit another. - (-ticksTillAddNextToken / addTokenTickInterval); - - // We mustn't exceed bucket capacity though. - var tokensToAdd = Math.Min(bucketCapacity, tokensMissedAdding); - - // Work out when tokens would next be due to be added, if we add these tokens. - var newAddNextTokenAtTicks = currentAddNextTokenAtTicks + (tokensToAdd * addTokenTickInterval); - // But if we were way overdue refilling the bucket (there was inactivity for a while), that value would be out-of-date: the next time we add tokens must be at least addTokenTickInterval from now. - newAddNextTokenAtTicks = Math.Max(newAddNextTokenAtTicks, now + addTokenTickInterval); +#if !NETSTANDARD2_0 + private SpinWait spinner = new(); +#endif - // Now see if we win the race to add these tokens. Other threads might be racing through this code at the same time: only one thread must add the tokens! - if (Interlocked.CompareExchange(ref addNextTokenAtTicks, newAddNextTokenAtTicks, currentAddNextTokenAtTicks) == currentAddNextTokenAtTicks) - { - // We won the race to add the tokens! + /// + /// Creates an instance of + /// + /// How often one execution is permitted. + /// The capacity of the token bucket. + /// This equates to the maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// + public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity) + { + if (onePer <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(onePer), onePer, $"The {nameof(LockFreeTokenBucketRateLimiter)} must specify a positive TimeSpan for how often an execution is permitted."); + if (bucketCapacity <= 0) throw new ArgumentOutOfRangeException(nameof(bucketCapacity), bucketCapacity, $"{nameof(bucketCapacity)} must be greater than or equal to 1."); - // Theoretically we want to add tokensToAdd tokens. But in fact we don't do that. - // We want to claim one of those tokens for ourselves - there's no way we're going to add it but let another thread snatch it from under our nose. - // (Doing that could leave this thread looping round adding tokens for ever which other threads just snatch - would lead to odd observed behaviour.) + addTokenTickInterval = onePer.Ticks; + this.bucketCapacity = bucketCapacity; - // So in fact we add (tokensToAdd - 1) tokens (ie we consume one), and return, permitting this execution. + currentTokens = bucketCapacity; + addNextTokenAtTicks = SystemClock.DateTimeOffsetUtcNow().Ticks + addTokenTickInterval; + } - // The advantage of only adding tokens when the bucket is empty is that we can now hard set the new amount of tokens (Interlocked.Exchange) without caring if other threads have simultaneously been taking or adding tokens. - // (If we added a token per addTokenTickInterval to a non-empty bucket, the reasoning about not overflowing the bucket seems harder.) - Interlocked.Exchange(ref currentTokens, tokensToAdd - 1); - return (true, TimeSpan.Zero); - } - else + /// + /// Returns whether the execution is permitted; if not, returns what should be waited before retrying. + /// + public (bool permitExecution, TimeSpan retryAfter) PermitExecution() + { + while (true) { - // We didn't win the race to add the tokens. BUT because it _was_ time to add tokens, another thread must have won that race and have added/be adding tokens, so there _may_ be more tokens, so loop and try again. - - // We want any thread refilling the bucket to have a chance to do so before we try to grab the next token. + // Try to get a token. + var tokensAfterGrabOne = Interlocked.Decrement(ref currentTokens); + + if (tokensAfterGrabOne >= 0) + { + // We got a token: permit execution! + return (true, TimeSpan.Zero); + } + + // No tokens! We're rate-limited - unless we can refill the bucket. + var now = SystemClock.DateTimeOffsetUtcNow().Ticks; + var currentAddNextTokenAtTicks = Interlocked.Read(ref addNextTokenAtTicks); + var ticksTillAddNextToken = currentAddNextTokenAtTicks - now; + + if (ticksTillAddNextToken > 0) + { + // Not time to add tokens yet: we're rate-limited! + return (false, TimeSpan.FromTicks(ticksTillAddNextToken)); + } + + // Time to add tokens to the bucket! + + // We definitely need to add one token. In fact, if we haven't hit this bit of code for a while, we might be due to add a bunch of tokens. + var tokensMissedAdding = + // Passing addNextTokenAtTicks merits one token + 1 + + // And any whole token tick intervals further each merit another. + (-ticksTillAddNextToken / addTokenTickInterval); + + // We mustn't exceed bucket capacity though. + var tokensToAdd = Math.Min(bucketCapacity, tokensMissedAdding); + + // Work out when tokens would next be due to be added, if we add these tokens. + var newAddNextTokenAtTicks = currentAddNextTokenAtTicks + (tokensToAdd * addTokenTickInterval); + // But if we were way overdue refilling the bucket (there was inactivity for a while), that value would be out-of-date: the next time we add tokens must be at least addTokenTickInterval from now. + newAddNextTokenAtTicks = Math.Max(newAddNextTokenAtTicks, now + addTokenTickInterval); + + // Now see if we win the race to add these tokens. Other threads might be racing through this code at the same time: only one thread must add the tokens! + if (Interlocked.CompareExchange(ref addNextTokenAtTicks, newAddNextTokenAtTicks, currentAddNextTokenAtTicks) == currentAddNextTokenAtTicks) + { + // We won the race to add the tokens! + + // Theoretically we want to add tokensToAdd tokens. But in fact we don't do that. + // We want to claim one of those tokens for ourselves - there's no way we're going to add it but let another thread snatch it from under our nose. + // (Doing that could leave this thread looping round adding tokens for ever which other threads just snatch - would lead to odd observed behaviour.) + + // So in fact we add (tokensToAdd - 1) tokens (ie we consume one), and return, permitting this execution. + + // The advantage of only adding tokens when the bucket is empty is that we can now hard set the new amount of tokens (Interlocked.Exchange) without caring if other threads have simultaneously been taking or adding tokens. + // (If we added a token per addTokenTickInterval to a non-empty bucket, the reasoning about not overflowing the bucket seems harder.) + Interlocked.Exchange(ref currentTokens, tokensToAdd - 1); + return (true, TimeSpan.Zero); + } + else + { + // We didn't win the race to add the tokens. BUT because it _was_ time to add tokens, another thread must have won that race and have added/be adding tokens, so there _may_ be more tokens, so loop and try again. + + // We want any thread refilling the bucket to have a chance to do so before we try to grab the next token. #if NETSTANDARD2_0 Thread.Sleep(0); #else - spinner.SpinOnce(); + spinner.SpinOnce(); #endif + } } } } -} \ No newline at end of file +} diff --git a/src/Polly/RateLimit/RateLimitEngine.cs b/src/Polly/RateLimit/RateLimitEngine.cs index aeed16b9da9..d94f9f9b3be 100644 --- a/src/Polly/RateLimit/RateLimitEngine.cs +++ b/src/Polly/RateLimit/RateLimitEngine.cs @@ -1,30 +1,31 @@ using System; using System.Threading; -namespace Polly.RateLimit; - -internal static class RateLimitEngine +namespace Polly.RateLimit { - internal static TResult Implementation( - IRateLimiter rateLimiter, - Func retryAfterFactory, - Func action, - Context context, - CancellationToken cancellationToken - ) + internal static class RateLimitEngine { - (var permit, var retryAfter) = rateLimiter.PermitExecution(); - - if (permit) + internal static TResult Implementation( + IRateLimiter rateLimiter, + Func retryAfterFactory, + Func action, + Context context, + CancellationToken cancellationToken + ) { - return action(context, cancellationToken); - } + (var permit, var retryAfter) = rateLimiter.PermitExecution(); - if (retryAfterFactory != null) - { - return retryAfterFactory(retryAfter, context); - } + if (permit) + { + return action(context, cancellationToken); + } + + if (retryAfterFactory != null) + { + return retryAfterFactory(retryAfter, context); + } - throw new RateLimitRejectedException(retryAfter); + throw new RateLimitRejectedException(retryAfter); + } } } \ No newline at end of file diff --git a/src/Polly/RateLimit/RateLimitPolicy.cs b/src/Polly/RateLimit/RateLimitPolicy.cs index 7a79dea59a1..aa0106f94f6 100644 --- a/src/Polly/RateLimit/RateLimitPolicy.cs +++ b/src/Polly/RateLimit/RateLimitPolicy.cs @@ -2,44 +2,45 @@ using System.Diagnostics; using System.Threading; -namespace Polly.RateLimit; - -/// -/// A rate-limit policy that can be applied to synchronous delegates. -/// -public class RateLimitPolicy : Policy, IRateLimitPolicy +namespace Polly.RateLimit { - private readonly IRateLimiter _rateLimiter; - - internal RateLimitPolicy(IRateLimiter rateLimiter) + /// + /// A rate-limit policy that can be applied to synchronous delegates. + /// + public class RateLimitPolicy : Policy, IRateLimitPolicy { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - } + private readonly IRateLimiter _rateLimiter; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RateLimitEngine.Implementation(_rateLimiter, null, action, context, cancellationToken); -} + internal RateLimitPolicy(IRateLimiter rateLimiter) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); + } -/// -/// A rate-limit policy that can be applied to synchronous delegates returning a value of type . -/// -public class RateLimitPolicy : Policy, IRateLimitPolicy -{ - private readonly IRateLimiter _rateLimiter; - private readonly Func _retryAfterFactory; + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RateLimitEngine.Implementation(_rateLimiter, null, action, context, cancellationToken); + } - internal RateLimitPolicy( - IRateLimiter rateLimiter, - Func retryAfterFactory) + /// + /// A rate-limit policy that can be applied to synchronous delegates returning a value of type . + /// + public class RateLimitPolicy : Policy, IRateLimitPolicy { - _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); - _retryAfterFactory = retryAfterFactory; - } + private readonly IRateLimiter _rateLimiter; + private readonly Func _retryAfterFactory; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RateLimitEngine.Implementation(_rateLimiter, _retryAfterFactory, action, context, cancellationToken); + internal RateLimitPolicy( + IRateLimiter rateLimiter, + Func retryAfterFactory) + { + _rateLimiter = rateLimiter ?? throw new ArgumentNullException(nameof(rateLimiter)); + _retryAfterFactory = retryAfterFactory; + } + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RateLimitEngine.Implementation(_rateLimiter, _retryAfterFactory, action, context, cancellationToken); + } } \ No newline at end of file diff --git a/src/Polly/RateLimit/RateLimitSyntax.cs b/src/Polly/RateLimit/RateLimitSyntax.cs index ad7001678ce..f73095b9577 100644 --- a/src/Polly/RateLimit/RateLimitSyntax.cs +++ b/src/Polly/RateLimit/RateLimitSyntax.cs @@ -1,49 +1,50 @@ using System; using Polly.RateLimit; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan) + public partial class Policy { - return RateLimit(numberOfExecutions, perTimeSpan, 1); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan) + { + return RateLimit(numberOfExecutions, perTimeSpan, 1); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new RateLimitPolicy(rateLimiter); + return new RateLimitPolicy(rateLimiter); + } } -} \ No newline at end of file +} diff --git a/src/Polly/RateLimit/RateLimitTResultSyntax.cs b/src/Polly/RateLimit/RateLimitTResultSyntax.cs index 9fb540a2d1e..45b67cae351 100644 --- a/src/Polly/RateLimit/RateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/RateLimitTResultSyntax.cs @@ -1,89 +1,90 @@ using System; using Polly.RateLimit; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan) + public partial class Policy { - return RateLimit(numberOfExecutions, perTimeSpan, null); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan) + { + return RateLimit(numberOfExecutions, perTimeSpan, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - Func retryAfterFactory) - { - return RateLimit(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// An (optional) factory to express the recommended retry-after time back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + Func retryAfterFactory) + { + return RateLimit(numberOfExecutions, perTimeSpan, 1, retryAfterFactory); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given. - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst) - { - return RateLimit(numberOfExecutions, perTimeSpan, maxBurst, null); - } + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given. + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst) + { + return RateLimit(numberOfExecutions, perTimeSpan, maxBurst, null); + } - /// - /// Builds a RateLimit that will rate-limit executions to per the timespan given, - /// with a maximum burst size of - /// - /// The type of return values this policy will handle. - /// The number of executions (call it N) permitted per timespan. - /// How often N executions are permitted. - /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). - /// This equates to the bucket-capacity of a token-bucket implementation. - /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. - /// If null, a with property will be thrown to indicate rate-limiting. - /// The policy instance. - public static RateLimitPolicy RateLimit( - int numberOfExecutions, - TimeSpan perTimeSpan, - int maxBurst, - Func retryAfterFactory) - { - if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); - if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); - if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); + /// + /// Builds a RateLimit that will rate-limit executions to per the timespan given, + /// with a maximum burst size of + /// + /// The type of return values this policy will handle. + /// The number of executions (call it N) permitted per timespan. + /// How often N executions are permitted. + /// The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while). + /// This equates to the bucket-capacity of a token-bucket implementation. + /// An (optional) factory to use to express retry-after back to the caller, when an operation is rate-limited. + /// If null, a with property will be thrown to indicate rate-limiting. + /// The policy instance. + public static RateLimitPolicy RateLimit( + int numberOfExecutions, + TimeSpan perTimeSpan, + int maxBurst, + Func retryAfterFactory) + { + if (numberOfExecutions < 1) throw new ArgumentOutOfRangeException(nameof(numberOfExecutions), numberOfExecutions, $"{nameof(numberOfExecutions)} per timespan must be an integer greater than or equal to 1."); + if (perTimeSpan <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, $"{nameof(perTimeSpan)} must be a positive timespan."); + if (maxBurst < 1) throw new ArgumentOutOfRangeException(nameof(maxBurst), maxBurst, $"{nameof(maxBurst)} must be an integer greater than or equal to 1."); - var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); + var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); - if (onePer <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); - } + if (onePer <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); + } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); - return new RateLimitPolicy(rateLimiter, retryAfterFactory); + return new RateLimitPolicy(rateLimiter, retryAfterFactory); + } } -} \ No newline at end of file +} diff --git a/src/Polly/RateLimit/RateLimiterFactory.cs b/src/Polly/RateLimit/RateLimiterFactory.cs index ce610c77fb3..e4f01ee29bc 100644 --- a/src/Polly/RateLimit/RateLimiterFactory.cs +++ b/src/Polly/RateLimit/RateLimiterFactory.cs @@ -1,9 +1,10 @@ using System; -namespace Polly.RateLimit; - -internal static class RateLimiterFactory +namespace Polly.RateLimit { - public static IRateLimiter Create(TimeSpan onePer, int bucketCapacity) - => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); -} \ No newline at end of file + internal static class RateLimiterFactory + { + public static IRateLimiter Create(TimeSpan onePer, int bucketCapacity) + => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity); + } +} diff --git a/src/Polly/Registry/IConcurrentPolicyRegistry.cs b/src/Polly/Registry/IConcurrentPolicyRegistry.cs index d87c217b877..e703c093856 100644 --- a/src/Polly/Registry/IConcurrentPolicyRegistry.cs +++ b/src/Polly/Registry/IConcurrentPolicyRegistry.cs @@ -1,89 +1,90 @@ using System; -namespace Polly.Registry; - -/// -/// Represents a collection of policies keyed by which can be updated and consumed in a thread-safe manner. -/// -/// The type of keys in the policy registry. -public interface IConcurrentPolicyRegistry : IPolicyRegistry +namespace Polly.Registry { /// - /// Adds an element with the provided key and policy to the registry. + /// Represents a collection of policies keyed by which can be updated and consumed in a thread-safe manner. /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// True if Policy was added. False otherwise. - bool TryAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; + /// The type of keys in the policy registry. + public interface IConcurrentPolicyRegistry : IPolicyRegistry + { + /// + /// Adds an element with the provided key and policy to the registry. + /// + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// True if Policy was added. False otherwise. + bool TryAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The of the policy to remove. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if the policy is successfully removed. Otherwise false. - bool TryRemove(TKey key, out TPolicy policy) where TPolicy : IsPolicy; + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The of the policy to remove. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if the policy is successfully removed. Otherwise false. + bool TryRemove(TKey key, out TPolicy policy) where TPolicy : IsPolicy; - /// - /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. - /// - /// - /// The key whose value is compared with comparisonPolicy, and possibly replaced. - /// The policy that replaces the value for the specified , if the comparison results in equality. - /// The policy that is compared to the existing policy at the specified key. - /// - bool TryUpdate(TKey key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy; + /// + /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. + /// + /// + /// The key whose value is compared with comparisonPolicy, and possibly replaced. + /// The policy that replaces the value for the specified , if the comparison results in equality. + /// The policy that is compared to the existing policy at the specified key. + /// + bool TryUpdate(TKey key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy; - /// - /// Adds a policy with the provided key and policy to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// The function used to generate a policy for the key - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy for the key as returned by policyFactory - /// if the key was not in the registry. - TPolicy GetOrAdd(TKey key, Func policyFactory) where TPolicy : IsPolicy; + /// + /// Adds a policy with the provided key and policy to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// The function used to generate a policy for the key + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy for the key as returned by policyFactory + /// if the key was not in the registry. + TPolicy GetOrAdd(TKey key, Func policyFactory) where TPolicy : IsPolicy; - /// - /// Adds a key/policy pair to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// the value to be added, if the key does not already exist - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy if the key was not in the registry. - TPolicy GetOrAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; + /// + /// Adds a key/policy pair to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// the value to be added, if the key does not already exist + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy if the key was not in the registry. + TPolicy GetOrAdd(TKey key, TPolicy policy) where TPolicy : IsPolicy; - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The function used to generate a policy for an absent key - /// The function used to generate a new policy for an existing key - /// based on the key's existing value - /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - TPolicy AddOrUpdate(TKey key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy; + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The function used to generate a policy for an absent key + /// The function used to generate a new policy for an existing key + /// based on the key's existing value + /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + TPolicy AddOrUpdate(TKey key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy; - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The policy to be added for an absent key - /// The function used to generate a new policy for an existing key based on - /// the key's existing value - /// The new policy for the key. This will be either be addPolicy (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - TPolicy AddOrUpdate(TKey key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy; -} \ No newline at end of file + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The policy to be added for an absent key + /// The function used to generate a new policy for an existing key based on + /// the key's existing value + /// The new policy for the key. This will be either be addPolicy (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + TPolicy AddOrUpdate(TKey key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy; + } +} diff --git a/src/Polly/Registry/IPolicyRegistry.cs b/src/Polly/Registry/IPolicyRegistry.cs index 1e85b8f3bd6..59f309b140d 100644 --- a/src/Polly/Registry/IPolicyRegistry.cs +++ b/src/Polly/Registry/IPolicyRegistry.cs @@ -1,44 +1,45 @@ using System; using System.Collections.Generic; -namespace Polly.Registry; - -/// -/// Represents a collection of policies keyed by . -/// -/// The type of keys in the policy registry. -public interface IPolicyRegistry : IReadOnlyPolicyRegistry +namespace Polly.Registry { /// - /// Adds an element with the provided key and policy to the registry. + /// Represents a collection of policies keyed by . /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// is null. - /// A Policy with same already exists. - void Add(TKey key, TPolicy policy) where TPolicy : IsPolicy; + /// The type of keys in the policy registry. + public interface IPolicyRegistry : IReadOnlyPolicyRegistry + { + /// + /// Adds an element with the provided key and policy to the registry. + /// + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// is null. + /// A Policy with same already exists. + void Add(TKey key, TPolicy policy) where TPolicy : IsPolicy; - /// - /// Gets or sets the with the specified key. - /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. - /// - /// The key of the value to get or set. - /// is null. - /// The given key was not present in the dictionary. - /// The value associated with the specified key. - new IsPolicy this[TKey key] { get; set; } + /// + /// Gets or sets the with the specified key. + /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. + /// + /// The key of the value to get or set. + /// is null. + /// The given key was not present in the dictionary. + /// The value associated with the specified key. + new IsPolicy this[TKey key] { get; set; } - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The key of the policy to remove. - /// True if the policy is successfully removed. Otherwise false. - /// is null. - bool Remove(TKey key); + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The key of the policy to remove. + /// True if the policy is successfully removed. Otherwise false. + /// is null. + bool Remove(TKey key); - /// - /// Removes all keys and policies from registry. - /// - void Clear(); -} \ No newline at end of file + /// + /// Removes all keys and policies from registry. + /// + void Clear(); + } +} diff --git a/src/Polly/Registry/IReadOnlyPolicyRegistry.cs b/src/Polly/Registry/IReadOnlyPolicyRegistry.cs index 42c876a32b1..b457578be51 100644 --- a/src/Polly/Registry/IReadOnlyPolicyRegistry.cs +++ b/src/Polly/Registry/IReadOnlyPolicyRegistry.cs @@ -1,55 +1,56 @@ using System; using System.Collections.Generic; -namespace Polly.Registry; - -/// -/// Represents a read-only collection of policies keyed by . -/// -/// The type of keys in the policy registry. -public interface IReadOnlyPolicyRegistry : IEnumerable> +namespace Polly.Registry { /// - /// Gets the with the specified key. - /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. + /// Represents a read-only collection of policies keyed by . /// - /// The key of the value to get or set. - /// is null. - /// The given key was not present in the dictionary. - /// The value associated with the specified key. - IsPolicy this[TKey key] { get; } + /// The type of keys in the policy registry. + public interface IReadOnlyPolicyRegistry : IEnumerable> + { + /// + /// Gets the with the specified key. + /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. + /// + /// The key of the value to get or set. + /// is null. + /// The given key was not present in the dictionary. + /// The value associated with the specified key. + IsPolicy this[TKey key] { get; } - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The type of Policy. - /// The policy stored in the registry under the given key. - /// is null. - TPolicy Get(TKey key) where TPolicy : IsPolicy; + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The type of Policy. + /// The policy stored in the registry under the given key. + /// is null. + TPolicy Get(TKey key) where TPolicy : IsPolicy; - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The key of the policy to get. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if Policy exists for the provided Key. False otherwise. - bool TryGet(TKey key, out TPolicy policy) where TPolicy : IsPolicy; + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The key of the policy to get. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if Policy exists for the provided Key. False otherwise. + bool TryGet(TKey key, out TPolicy policy) where TPolicy : IsPolicy; - /// - /// Total number of policies in the registry. - /// - int Count { get; } + /// + /// Total number of policies in the registry. + /// + int Count { get; } - /// - /// Determines whether the specified exists. - /// - /// The Key to locate in the registry - /// True if exists otherwise false - /// is null - bool ContainsKey(TKey key); + /// + /// Determines whether the specified exists. + /// + /// The Key to locate in the registry + /// True if exists otherwise false + /// is null + bool ContainsKey(TKey key); + } } \ No newline at end of file diff --git a/src/Polly/Registry/PolicyRegistry.cs b/src/Polly/Registry/PolicyRegistry.cs index 703f687c5d1..ab76b4b8df3 100644 --- a/src/Polly/Registry/PolicyRegistry.cs +++ b/src/Polly/Registry/PolicyRegistry.cs @@ -3,269 +3,270 @@ using System.Collections.Generic; using System.Collections.Concurrent; -namespace Polly.Registry; - -/// -/// -/// Stores a registry of and policy pairs. -/// -/// Uses ConcurrentDictionary to store the collection. -public class PolicyRegistry : IConcurrentPolicyRegistry +namespace Polly.Registry { - private readonly IDictionary _registry = new ConcurrentDictionary(); - + /// /// - /// Creates a registry of policies with keys. + /// Stores a registry of and policy pairs. /// - public PolicyRegistry() + /// Uses ConcurrentDictionary to store the collection. + public class PolicyRegistry : IConcurrentPolicyRegistry { - // This empty public constructor must be retained while the adjacent internal constructor exists for testing. - // The integration with HttpClientFactory, method services.AddPolicyRegistry(), depends on this empty public constructor. - // Do not collapse the two constructors into a constructor with optional parameter registry == null. - // That breaks the requirement for a noargs public constructor, against which nuget-published .NET Core dlls have been compiled. - } + private readonly IDictionary _registry = new ConcurrentDictionary(); - /// - /// Creates a registry of policies with keys. - /// This internal constructor exists solely to facilitate testing of the GetEnumerator() methods, which allow us to support collection initialisation syntax. - /// - /// a dictionary containing keys and policies used for testing. - internal PolicyRegistry(IDictionary registry) - { - _registry = registry ?? throw new NullReferenceException(nameof(registry)); - } + /// + /// Creates a registry of policies with keys. + /// + public PolicyRegistry() + { + // This empty public constructor must be retained while the adjacent internal constructor exists for testing. + // The integration with HttpClientFactory, method services.AddPolicyRegistry(), depends on this empty public constructor. + // Do not collapse the two constructors into a constructor with optional parameter registry == null. + // That breaks the requirement for a noargs public constructor, against which nuget-published .NET Core dlls have been compiled. + } - private ConcurrentDictionary ThrowIfNotConcurrentImplementation() - { - if (_registry is ConcurrentDictionary concurrentRegistry) + /// + /// Creates a registry of policies with keys. + /// This internal constructor exists solely to facilitate testing of the GetEnumerator() methods, which allow us to support collection initialisation syntax. + /// + /// a dictionary containing keys and policies used for testing. + internal PolicyRegistry(IDictionary registry) { - return concurrentRegistry; + _registry = registry ?? throw new NullReferenceException(nameof(registry)); } - throw new InvalidOperationException($"This {nameof(PolicyRegistry)} is not configured for concurrent operations. This exception should never be thrown in production code as the only public constructors create {nameof(PolicyRegistry)} instances of the correct form."); - } + private ConcurrentDictionary ThrowIfNotConcurrentImplementation() + { + if (_registry is ConcurrentDictionary concurrentRegistry) + { + return concurrentRegistry; + } - /// - /// Total number of policies in the registry. - /// - public int Count => _registry.Count; + throw new InvalidOperationException($"This {nameof(PolicyRegistry)} is not configured for concurrent operations. This exception should never be thrown in production code as the only public constructors create {nameof(PolicyRegistry)} instances of the correct form."); + } - /// - /// Adds a policy with the provided key and policy to the registry. - /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// is null. - /// A Policy with same already exists. - public void Add(string key, TPolicy policy) where TPolicy : IsPolicy => - _registry.Add(key, policy); + /// + /// Total number of policies in the registry. + /// + public int Count => _registry.Count; - /// - /// Adds a policy with the provided key and policy to the registry. - /// - /// The key for the policy. - /// The policy to store in the registry. - /// The type of Policy. - /// True if Policy was added. False otherwise. - public bool TryAdd(string key, TPolicy policy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Adds a policy with the provided key and policy to the registry. + /// + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// is null. + /// A Policy with same already exists. + public void Add(string key, TPolicy policy) where TPolicy : IsPolicy => + _registry.Add(key, policy); - return registry.TryAdd(key, policy); - } + /// + /// Adds a policy with the provided key and policy to the registry. + /// + /// The key for the policy. + /// The policy to store in the registry. + /// The type of Policy. + /// True if Policy was added. False otherwise. + public bool TryAdd(string key, TPolicy policy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - /// - /// Gets of sets the with the specified key. - /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. - /// - /// The key of the policy to get or set. - /// is null. - /// The given key was not present in the registry. - /// The policy associated with the specified key. - public IsPolicy this[string key] - { - get => _registry[key]; - set => _registry[key] = value; - } + return registry.TryAdd(key, policy); + } - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The type of Policy. - /// The policy stored in the registry under the given key. - /// is null. - /// The given key was not present in the registry. - public TPolicy Get(string key) where TPolicy : IsPolicy => - (TPolicy) _registry[key]; + /// + /// Gets of sets the with the specified key. + /// To retrieve a policy directly as a particular Policy type or Policy interface (avoiding a cast), use the method. + /// + /// The key of the policy to get or set. + /// is null. + /// The given key was not present in the registry. + /// The policy associated with the specified key. + public IsPolicy this[string key] + { + get => _registry[key]; + set => _registry[key] = value; + } - /// - /// Gets the policy stored under the provided key, casting to . - /// - /// The key of the policy to get. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if Policy exists for the provided Key. False otherwise. - public bool TryGet(string key, out TPolicy policy) where TPolicy : IsPolicy - { - var got = _registry.TryGetValue(key, out var value); - policy = got ? (TPolicy)value : default; - return got; - } + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The type of Policy. + /// The policy stored in the registry under the given key. + /// is null. + /// The given key was not present in the registry. + public TPolicy Get(string key) where TPolicy : IsPolicy => + (TPolicy) _registry[key]; - /// - /// Removes all keys and policies from registry. - /// - public void Clear() => - _registry.Clear(); + /// + /// Gets the policy stored under the provided key, casting to . + /// + /// The key of the policy to get. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if Policy exists for the provided Key. False otherwise. + public bool TryGet(string key, out TPolicy policy) where TPolicy : IsPolicy + { + var got = _registry.TryGetValue(key, out var value); + policy = got ? (TPolicy)value : default; + return got; + } - /// - /// Determines whether the specified exists. - /// - /// The key to locate in the registry. - /// True if exists otherwise false. - /// is null. - public bool ContainsKey(string key) => - _registry.ContainsKey(key); + /// + /// Removes all keys and policies from registry. + /// + public void Clear() => + _registry.Clear(); - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The of the policy to remove. - /// True if the policy is successfully removed. Otherwise false. - /// is null. - public bool Remove(string key) => - _registry.Remove(key); + /// + /// Determines whether the specified exists. + /// + /// The key to locate in the registry. + /// True if exists otherwise false. + /// is null. + public bool ContainsKey(string key) => + _registry.ContainsKey(key); - /// - /// Removes the policy stored under the specified from the registry. - /// - /// The of the policy to remove. - /// - /// This method returns the policy associated with the specified , if the - /// key is found; otherwise null. - /// This parameter is passed uninitialized. - /// - /// The type of Policy. - /// True if the policy is successfully removed. Otherwise false. - public bool TryRemove(string key, out TPolicy policy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The of the policy to remove. + /// True if the policy is successfully removed. Otherwise false. + /// is null. + public bool Remove(string key) => + _registry.Remove(key); - var got = registry.TryRemove(key, out var value); - policy = got ? (TPolicy) value : default; - return got; - } + /// + /// Removes the policy stored under the specified from the registry. + /// + /// The of the policy to remove. + /// + /// This method returns the policy associated with the specified , if the + /// key is found; otherwise null. + /// This parameter is passed uninitialized. + /// + /// The type of Policy. + /// True if the policy is successfully removed. Otherwise false. + public bool TryRemove(string key, out TPolicy policy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - /// - /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. - /// - /// - /// The key whose value is compared with comparisonPolicy, and possibly replaced. - /// The policy that replaces the value for the specified , if the comparison results in equality. - /// The policy that is compared to the existing policy at the specified key. - /// - public bool TryUpdate(string key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + var got = registry.TryRemove(key, out var value); + policy = got ? (TPolicy) value : default; + return got; + } - return registry.TryUpdate(key, newPolicy, comparisonPolicy); - } + /// + /// Compares the existing policy for the specified key with a specified policy, and if they are equal, updates the policy with a third value. + /// + /// + /// The key whose value is compared with comparisonPolicy, and possibly replaced. + /// The policy that replaces the value for the specified , if the comparison results in equality. + /// The policy that is compared to the existing policy at the specified key. + /// + public bool TryUpdate(string key, TPolicy newPolicy, TPolicy comparisonPolicy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - /// - /// Adds a policy with the provided key and policy to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// The function used to generate a policy for the key - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy for the key as returned by policyFactory - /// if the key was not in the registry. - public TPolicy GetOrAdd(string key, Func policyFactory) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + return registry.TryUpdate(key, newPolicy, comparisonPolicy); + } - return (TPolicy) registry.GetOrAdd(key, k => policyFactory(k)); - } + /// + /// Adds a policy with the provided key and policy to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// The function used to generate a policy for the key + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy for the key as returned by policyFactory + /// if the key was not in the registry. + public TPolicy GetOrAdd(string key, Func policyFactory) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - /// - /// Adds a key/policy pair to the registry - /// if the key does not already exist. - /// - /// The key of the policy to add. - /// the policy to be added, if the key does not already exist - /// The policy for the key. This will be either the existing policy for the key if the - /// key is already in the registry, or the new policy if the key was not in the registry. - public TPolicy GetOrAdd(string key, TPolicy policy) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + return (TPolicy) registry.GetOrAdd(key, k => policyFactory(k)); + } - return (TPolicy) registry.GetOrAdd(key, policy); - } + /// + /// Adds a key/policy pair to the registry + /// if the key does not already exist. + /// + /// The key of the policy to add. + /// the policy to be added, if the key does not already exist + /// The policy for the key. This will be either the existing policy for the key if the + /// key is already in the registry, or the new policy if the key was not in the registry. + public TPolicy GetOrAdd(string key, TPolicy policy) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The function used to generate a policy for an absent key - /// The function used to generate a new policy for an existing key - /// based on the key's existing value - /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - public TPolicy AddOrUpdate(string key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + return (TPolicy) registry.GetOrAdd(key, policy); + } - return (TPolicy) registry.AddOrUpdate(key, k => addPolicyFactory(k), (k, e) => updatePolicyFactory(k, (TPolicy)e)); - } + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The function used to generate a policy for an absent key + /// The function used to generate a new policy for an existing key + /// based on the key's existing value + /// The new policy for the key. This will be either be the result of addPolicyFactory (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + public TPolicy AddOrUpdate(string key, Func addPolicyFactory, Func updatePolicyFactory) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - /// - /// Adds a key/policy pair to the registry if the key does not already - /// exist, or updates a key/policy pair in the registry if the key - /// already exists. - /// - /// The key to be added or whose policy should be updated - /// The policy to be added for an absent key - /// The function used to generate a new policy for an existing key based on - /// the key's existing value - /// The new policy for the key. This will be either be addPolicy (if the key was - /// absent) or the result of updatePolicyFactory (if the key was present). - public TPolicy AddOrUpdate(string key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy - { - var registry = ThrowIfNotConcurrentImplementation(); + return (TPolicy) registry.AddOrUpdate(key, k => addPolicyFactory(k), (k, e) => updatePolicyFactory(k, (TPolicy)e)); + } - return (TPolicy)registry.AddOrUpdate(key, addPolicy, (k, e) => updatePolicyFactory(k, (TPolicy)e)); - } + /// + /// Adds a key/policy pair to the registry if the key does not already + /// exist, or updates a key/policy pair in the registry if the key + /// already exists. + /// + /// The key to be added or whose policy should be updated + /// The policy to be added for an absent key + /// The function used to generate a new policy for an existing key based on + /// the key's existing value + /// The new policy for the key. This will be either be addPolicy (if the key was + /// absent) or the result of updatePolicyFactory (if the key was present). + public TPolicy AddOrUpdate(string key, TPolicy addPolicy, Func updatePolicyFactory) where TPolicy : IsPolicy + { + var registry = ThrowIfNotConcurrentImplementation(); - /// Returns an enumerator that iterates through the policy objects in the . - /// An enumerator for the . - /// - /// The enumerator returned from the registry is safe to use concurrently with - /// reads and writes to the registry, however it does not represent a moment-in-time snapshot - /// of the registry's contents. The contents exposed through the enumerator may contain modifications - /// made to the registry after was called. - /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, - /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. - /// - public IEnumerator> GetEnumerator() => _registry.GetEnumerator(); + return (TPolicy)registry.AddOrUpdate(key, addPolicy, (k, e) => updatePolicyFactory(k, (TPolicy)e)); + } - /// Returns an enumerator that iterates through the policy objects in the . - /// An enumerator for the . - /// - /// The enumerator returned from the registry is safe to use concurrently with - /// reads and writes to the registry, however it does not represent a moment-in-time snapshot - /// of the registry's contents. The contents exposed through the enumerator may contain modifications - /// made to the registry after was called. - /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, - /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + /// Returns an enumerator that iterates through the policy objects in the . + /// An enumerator for the . + /// + /// The enumerator returned from the registry is safe to use concurrently with + /// reads and writes to the registry, however it does not represent a moment-in-time snapshot + /// of the registry's contents. The contents exposed through the enumerator may contain modifications + /// made to the registry after was called. + /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, + /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. + /// + public IEnumerator> GetEnumerator() => _registry.GetEnumerator(); + + /// Returns an enumerator that iterates through the policy objects in the . + /// An enumerator for the . + /// + /// The enumerator returned from the registry is safe to use concurrently with + /// reads and writes to the registry, however it does not represent a moment-in-time snapshot + /// of the registry's contents. The contents exposed through the enumerator may contain modifications + /// made to the registry after was called. + /// This is not considered a significant issue as typical usage of PolicyRegistry is for bulk population at app startup, + /// with only infrequent changes to the PolicyRegistry during app running, if using PolicyRegistry for dynamic updates during running. + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } } \ No newline at end of file diff --git a/src/Polly/ResultPredicate.cs b/src/Polly/ResultPredicate.cs index 720f00d2e88..c597981ff9c 100644 --- a/src/Polly/ResultPredicate.cs +++ b/src/Polly/ResultPredicate.cs @@ -1,9 +1,11 @@ -namespace Polly; +namespace Polly +{ + /// + /// A predicate that can be run against a passed result value of type . Predicates are used to define whether policies handle the given result. + /// + /// The passed result, against which to evaluate the predicate. + /// The type of results which this predicate can evaluate. + /// True if the passed matched the predicate; otherwise, false. -/// -/// A predicate that can be run against a passed result value of type . Predicates are used to define whether policies handle the given result. -/// -/// The passed result, against which to evaluate the predicate. -/// The type of results which this predicate can evaluate. -/// True if the passed matched the predicate; otherwise, false. -public delegate bool ResultPredicate(TResult result); \ No newline at end of file + public delegate bool ResultPredicate(TResult result); +} \ No newline at end of file diff --git a/src/Polly/ResultPredicates.cs b/src/Polly/ResultPredicates.cs index 8664368210e..1593148bac1 100644 --- a/src/Polly/ResultPredicates.cs +++ b/src/Polly/ResultPredicates.cs @@ -1,35 +1,37 @@ using System.Collections.Generic; using System.Linq; -namespace Polly; - -/// -/// A collection of predicates used to define whether a policy handles a given value. -/// -public class ResultPredicates +namespace Polly { - private List> _predicates; - - internal void Add(ResultPredicate predicate) - { - _predicates = _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.Add(predicate); - } - /// - /// Returns a bool indicating whether the passed value matched any predicates. + /// A collection of predicates used to define whether a policy handles a given value. /// - /// The value to assess against the predicates. - public bool AnyMatch(TResult result) + public class ResultPredicates { - if (_predicates == null) return false; + private List> _predicates; - return _predicates.Any(predicate => predicate(result)); + internal void Add(ResultPredicate predicate) + { + _predicates = _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.Add(predicate); + } + + /// + /// Returns a bool indicating whether the passed value matched any predicates. + /// + /// The value to assess against the predicates. + public bool AnyMatch(TResult result) + { + if (_predicates == null) return false; + + return _predicates.Any(predicate => predicate(result)); + } + + /// + /// Specifies that no result-handling filters are applied or are required. + /// + public static readonly ResultPredicates None = new ResultPredicates(); } - /// - /// Specifies that no result-handling filters are applied or are required. - /// - public static readonly ResultPredicates None = new ResultPredicates(); } \ No newline at end of file diff --git a/src/Polly/Retry/AsyncRetryEngine.cs b/src/Polly/Retry/AsyncRetryEngine.cs index 7bec5052d52..0188d983bdc 100644 --- a/src/Polly/Retry/AsyncRetryEngine.cs +++ b/src/Polly/Retry/AsyncRetryEngine.cs @@ -4,86 +4,87 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Retry; - -internal static class AsyncRetryEngine +namespace Polly.Retry { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldRetryExceptionPredicates, - ResultPredicates shouldRetryResultPredicates, - Func, TimeSpan, int, Context, Task> onRetryAsync, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null, - bool continueOnCapturedContext = false) + internal static class AsyncRetryEngine { - var tryCount = 0; - var sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); - - try + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldRetryExceptionPredicates, + ResultPredicates shouldRetryResultPredicates, + Func, TimeSpan, int, Context, Task> onRetryAsync, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null, + bool continueOnCapturedContext = false) { - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - - bool canRetry; - DelegateResult outcome; + var tryCount = 0; + var sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); - try + try + { + while (true) { - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + cancellationToken.ThrowIfCancellationRequested(); + + bool canRetry; + DelegateResult outcome; - if (!shouldRetryResultPredicates.AnyMatch(result)) + try { - return result; - } + var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + if (!shouldRetryResultPredicates.AnyMatch(result)) + { + return result; + } - if (!canRetry) - { - return result; - } + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); - outcome = new DelegateResult(result); - } - catch (Exception ex) - { - var handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; + if (!canRetry) + { + return result; + } + + outcome = new DelegateResult(result); } + catch (Exception ex) + { + var handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); - if (!canRetry) - { - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); - throw; - } + if (!canRetry) + { + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; + } - outcome = new DelegateResult(handledException); - } + outcome = new DelegateResult(handledException); + } - if (tryCount < int.MaxValue) { tryCount++; } + if (tryCount < int.MaxValue) { tryCount++; } - var waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); + var waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); - await onRetryAsync(outcome, waitDuration, tryCount, context).ConfigureAwait(continueOnCapturedContext); + await onRetryAsync(outcome, waitDuration, tryCount, context).ConfigureAwait(continueOnCapturedContext); - if (waitDuration > TimeSpan.Zero) - { - await SystemClock.SleepAsync(waitDuration, cancellationToken).ConfigureAwait(continueOnCapturedContext); + if (waitDuration > TimeSpan.Zero) + { + await SystemClock.SleepAsync(waitDuration, cancellationToken).ConfigureAwait(continueOnCapturedContext); + } } } - } - finally - { - sleepDurationsEnumerator?.Dispose(); + finally + { + sleepDurationsEnumerator?.Dispose(); + } } } } \ No newline at end of file diff --git a/src/Polly/Retry/AsyncRetryPolicy.cs b/src/Polly/Retry/AsyncRetryPolicy.cs index 359a6c83f24..2abd3f4cd1d 100644 --- a/src/Polly/Retry/AsyncRetryPolicy.cs +++ b/src/Polly/Retry/AsyncRetryPolicy.cs @@ -4,94 +4,96 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Retry; - -/// -/// A retry policy that can be applied to asynchronous delegates. -/// -public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy +namespace Polly.Retry { - private readonly Func _onRetryAsync; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func _sleepDurationProvider; - - internal AsyncRetryPolicy( - PolicyBuilder policyBuilder, - Func onRetryAsync, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func sleepDurationProvider = null - ) - : base(policyBuilder) + /// + /// A retry policy that can be applied to asynchronous delegates. + /// + public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); + private readonly Func _onRetryAsync; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func _sleepDurationProvider; + + internal AsyncRetryPolicy( + PolicyBuilder policyBuilder, + Func onRetryAsync, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func sleepDurationProvider = null + ) + : base(policyBuilder) + { + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); + } + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncRetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + (outcome, timespan, retryCount, ctx) => _onRetryAsync(outcome.Exception, timespan, retryCount, ctx), + _permittedRetryCount, + _sleepDurationsEnumerable, + _sleepDurationProvider != null + ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) + : (Func, Context, TimeSpan>)null, + continueOnCapturedContext + ); + } } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) + /// + /// A retry policy that can be applied to asynchronous delegates returning a value of type . + /// + public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy { - return AsyncRetryEngine.ImplementationAsync( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, timespan, retryCount, ctx) => _onRetryAsync(outcome.Exception, timespan, retryCount, ctx), - _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider != null - ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) - : (Func, Context, TimeSpan>)null, - continueOnCapturedContext - ); - } -} + private readonly Func, TimeSpan, int, Context, Task> _onRetryAsync; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func, Context, TimeSpan> _sleepDurationProvider; -/// -/// A retry policy that can be applied to asynchronous delegates returning a value of type . -/// -public class AsyncRetryPolicy : AsyncPolicy, IRetryPolicy -{ - private readonly Func, TimeSpan, int, Context, Task> _onRetryAsync; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func, Context, TimeSpan> _sleepDurationProvider; + internal AsyncRetryPolicy( + PolicyBuilder policyBuilder, + Func, TimeSpan, int, Context, Task> onRetryAsync, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null + ) + : base(policyBuilder) + { + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); + } - internal AsyncRetryPolicy( - PolicyBuilder policyBuilder, - Func, TimeSpan, int, Context, Task> onRetryAsync, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null - ) - : base(policyBuilder) - { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetryAsync = onRetryAsync ?? throw new ArgumentNullException(nameof(onRetryAsync)); + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncRetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onRetryAsync, + _permittedRetryCount, + _sleepDurationsEnumerable, + _sleepDurationProvider, + continueOnCapturedContext + ); } +} - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncRetryEngine.ImplementationAsync( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _onRetryAsync, - _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider, - continueOnCapturedContext - ); -} \ No newline at end of file diff --git a/src/Polly/Retry/AsyncRetrySyntax.cs b/src/Polly/Retry/AsyncRetrySyntax.cs index 2ce6273bfb4..9443c0e49bf 100644 --- a/src/Polly/Retry/AsyncRetrySyntax.cs +++ b/src/Polly/Retry/AsyncRetrySyntax.cs @@ -4,1104 +4,1106 @@ using System.Threading.Tasks; using Polly.Retry; -namespace Polly; - -/// -/// Fluent API for defining a . -/// -public static class AsyncRetrySyntax +namespace Polly { /// - /// Builds an that will retry once. + /// Fluent API for defining a . /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) - => policyBuilder.RetryAsync(1); - - /// - /// Builds an that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) + public static class AsyncRetrySyntax { - Action doNothing = (_, _, _) => { }; - - return policyBuilder.RetryAsync(retryCount, onRetry: doNothing); - } - - /// - /// Builds an that will retry once - /// calling on retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.RetryAsync(1, + /// + /// Builds an that will retry once. + /// + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) + => policyBuilder.RetryAsync(1); + + /// + /// Builds an that will retry times. + /// + /// The policy builder. + /// The retry count. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) + { + Action doNothing = (_, _, _) => { }; + + return policyBuilder.RetryAsync(retryCount, onRetry: doNothing); + } + + /// + /// Builds an that will retry once + /// calling on retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.RetryAsync(1, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - - /// - /// Builds an that will retry once - /// calling on retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + ); + + /// + /// Builds an that will retry once + /// calling on retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry once - /// calling on retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.RetryAsync(1, onRetry); - - /// - /// Builds an that will retry once - /// calling on retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync); - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + ); + } + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry once + /// calling on retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.RetryAsync(1, onRetry); + + /// + /// Builds an that will retry once + /// calling on retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync); + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), - retryCount - ); - } - - /// - /// Builds an that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) - { - Action doNothing = _ => { }; - - return policyBuilder.RetryForeverAsync(doNothing); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry times + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), + retryCount + ); + } + + /// + /// Builds an that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) + { + Action doNothing = _ => { }; + + return policyBuilder.RetryForeverAsync(doNothing); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (Exception outcome, Context _) => onRetry(outcome) + onRetryAsync: async (Exception outcome, Context _) => onRetry(outcome) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (Exception outcome, Context _) => onRetryAsync(outcome)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (Exception outcome, Context _) => onRetryAsync(outcome)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) + onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) - ); - } - - /// - /// Builds an that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) + ); + } + + /// + /// Builds an that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) + onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) + onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - var sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + var sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryAsync( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryAsync( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationProvider: sleepDurationProvider + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) + onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) + onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetry(exception, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, i, timespan, _) => onRetry(exception, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetryAsync(exception, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, i, timespan, _) => onRetryAsync(exception, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func onRetryAsync) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetry(exception, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, i, timespan, _) => onRetry(exception, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetryAsync(exception, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, i, timespan, _) => onRetryAsync(exception, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (exception, timespan, ctx) => onRetry(exception, timespan, ctx) + async (exception, timespan, ctx) => onRetry(exception, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (exception, i, timespan, ctx) => onRetry(exception, i, timespan, ctx) + async (exception, i, timespan, ctx) => onRetry(exception, i, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds an that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider + ); + } } +} - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds an that will wait and retry indefinitely - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), - sleepDurationProvider: sleepDurationProvider - ); - } -} \ No newline at end of file diff --git a/src/Polly/Retry/AsyncRetryTResultSyntax.cs b/src/Polly/Retry/AsyncRetryTResultSyntax.cs index 2e67860071f..6d7cc6993a1 100644 --- a/src/Polly/Retry/AsyncRetryTResultSyntax.cs +++ b/src/Polly/Retry/AsyncRetryTResultSyntax.cs @@ -4,1102 +4,1104 @@ using System.Threading.Tasks; using Polly.Retry; -namespace Polly; - -/// -/// Fluent API for defining an . -/// -public static class AsyncRetryTResultSyntax +namespace Polly { /// - /// Builds an that will retry once. + /// Fluent API for defining an . /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) - => policyBuilder.RetryAsync(1); - - /// - /// Builds an that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) + public static class AsyncRetryTResultSyntax { - Action, int> doNothing = (_, _) => { }; - - return policyBuilder.RetryAsync(retryCount, doNothing); - } - - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) - => policyBuilder.RetryAsync(1, + /// + /// Builds an that will retry once. + /// + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder) + => policyBuilder.RetryAsync(1); + + /// + /// Builds an that will retry times. + /// + /// The policy builder. + /// The retry count. + /// The policy instance. + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount) + { + Action, int> doNothing = (_, _) => { }; + + return policyBuilder.RetryAsync(retryCount, doNothing); + } + + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) + => policyBuilder.RetryAsync(1, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + ); + + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - => policyBuilder.RetryAsync(1, onRetry); - - /// - /// Builds an that will retry once - /// calling on retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) - => policyBuilder.RetryAsync(1, onRetryAsync); - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetry - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryAsync(retryCount, + ); + } + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryAsync(retryCount, onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + => policyBuilder.RetryAsync(1, onRetry); + + /// + /// Builds an that will retry once + /// calling on retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) + => policyBuilder.RetryAsync(1, onRetryAsync); + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetry + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryAsync(retryCount, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry times - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than zero. - /// onRetryAsync - public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Context, Task> onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), - retryCount - ); - } - - /// - /// Builds an that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) - { - Action> doNothing = _ => { }; - - return policyBuilder.RetryForeverAsync(doNothing); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry times + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than zero. + /// onRetryAsync + public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, int, Context, Task> onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx), + retryCount + ); + } + + /// + /// Builds an that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder) + { + Action> doNothing = _ => { }; + + return policyBuilder.RetryForeverAsync(doNothing); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (DelegateResult outcome, Context _) => onRetry(outcome) + onRetryAsync: async (DelegateResult outcome, Context _) => onRetry(outcome) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, _) => onRetry(outcome, i) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (DelegateResult outcome, Context _) => onRetryAsync(outcome)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (DelegateResult outcome, Context _) => onRetryAsync(outcome)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, _) => onRetryAsync(outcome, i)); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) + onRetryAsync: async (outcome, ctx) => onRetry(outcome, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForeverAsync( + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) - ); - } - - /// - /// Builds an that will retry indefinitely - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// onRetryAsync - public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) - ); - } - - /// - /// Builds an that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, _, ctx) => onRetryAsync(outcome, ctx) + ); + } + + /// + /// Builds an that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static AsyncRetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx) + ); + } + + /// + /// Builds an that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryAsync(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) + onRetryAsync: async (outcome, span, _, _) => onRetry(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) + onRetryAsync: (outcome, span, _, _) => onRetryAsync(outcome, span) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - var sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + var sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + onRetryAsync: async (outcome, span, _, ctx) => onRetry(outcome, span, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - retryCount, - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + retryCount, + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryAsync( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync); - } - - /// - /// Builds an that will wait and retry times - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), result of previous execution, and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryAsync( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync); + } + + /// + /// Builds an that will wait and retry times + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), result of previous execution, and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, + Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + retryCount, + sleepDurationProvider: sleepDurationProvider + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryAsync(sleepDurations, doNothing); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) + onRetryAsync: async (outcome, timespan, _, _) => onRetry(outcome, timespan) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, _) => onRetryAsync(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) + onRetryAsync: async (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx) #pragma warning restore 1998 - ); - - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, - onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryAsync( - sleepDurations, + ); + + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, + onRetryAsync: (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx) + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryAsync( + sleepDurations, #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) + onRetryAsync: async (outcome, timespan, i, ctx) => onRetry(outcome, timespan, i, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry as many times as there are provided - /// - /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetryAsync - /// - public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, int, Context, Task> onRetryAsync) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - onRetryAsync, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan, Context> doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, timespan, _) => onRetry(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, i, timespan, _) => onRetry(outcome, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, timespan, _) => onRetryAsync(outcome, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return policyBuilder.WaitAndRetryForeverAsync( - (retryCount, _) => sleepDurationProvider(retryCount), - (outcome, i, timespan, _) => onRetryAsync(outcome, i, timespan) - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry as many times as there are provided + /// + /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetryAsync + /// + public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Func, TimeSpan, int, Context, Task> onRetryAsync) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + onRetryAsync, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan, Context> doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForeverAsync(sleepDurationProvider, doNothing); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, timespan, _) => onRetry(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, i, timespan, _) => onRetry(outcome, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, timespan, _) => onRetryAsync(outcome, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, _) => sleepDurationProvider(retryCount), + (outcome, i, timespan, _) => onRetryAsync(outcome, i, timespan) + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (outcome, timespan, ctx) => onRetry(outcome, timespan, ctx) + async (outcome, timespan, ctx) => onRetry(outcome, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForeverAsync( - sleepDurationProvider, + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, #pragma warning disable 1998 // async method has no awaits, will run synchronously - async (outcome, i, timespan, ctx) => onRetry(outcome, i, timespan, ctx) + async (outcome, i, timespan, ctx) => onRetry(outcome, i, timespan, ctx) #pragma warning restore 1998 - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds an that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new AsyncRetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider + ); + } } +} - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForeverAsync( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetryAsync - ); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetryAsync(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds an that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call asynchronously on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetryAsync - public static AsyncRetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - return new AsyncRetryPolicy( - policyBuilder, - (exception, timespan, i, ctx) => onRetryAsync(exception, i, timespan, ctx), - sleepDurationProvider: sleepDurationProvider - ); - } -} \ No newline at end of file diff --git a/src/Polly/Retry/IRetryPolicy.cs b/src/Polly/Retry/IRetryPolicy.cs index 76f85a8fd48..89daedbbc6d 100644 --- a/src/Polly/Retry/IRetryPolicy.cs +++ b/src/Polly/Retry/IRetryPolicy.cs @@ -1,16 +1,18 @@ -namespace Polly.Retry; - -/// -/// Defines properties and methods common to all Retry policies. -/// -public interface IRetryPolicy : IsPolicy +namespace Polly.Retry { -} + /// + /// Defines properties and methods common to all Retry policies. + /// -/// -/// Defines properties and methods common to all Retry policies generic-typed for executions returning results of type . -/// -public interface IRetryPolicy : IRetryPolicy -{ + public interface IRetryPolicy : IsPolicy + { + } -} \ No newline at end of file + /// + /// Defines properties and methods common to all Retry policies generic-typed for executions returning results of type . + /// + public interface IRetryPolicy : IRetryPolicy + { + + } +} diff --git a/src/Polly/Retry/RetryEngine.cs b/src/Polly/Retry/RetryEngine.cs index cbf19e07c64..5679a12a1bd 100644 --- a/src/Polly/Retry/RetryEngine.cs +++ b/src/Polly/Retry/RetryEngine.cs @@ -3,86 +3,87 @@ using System.Threading; using Polly.Utilities; -namespace Polly.Retry; - -internal static class RetryEngine +namespace Polly.Retry { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - ExceptionPredicates shouldRetryExceptionPredicates, - ResultPredicates shouldRetryResultPredicates, - Action, TimeSpan, int, Context> onRetry, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null) + internal static class RetryEngine { - var tryCount = 0; - var sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); - - try + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + ExceptionPredicates shouldRetryExceptionPredicates, + ResultPredicates shouldRetryResultPredicates, + Action, TimeSpan, int, Context> onRetry, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null) { - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - - bool canRetry; - DelegateResult outcome; + var tryCount = 0; + var sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); - try + try + { + while (true) { - var result = action(context, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + bool canRetry; + DelegateResult outcome; - if (!shouldRetryResultPredicates.AnyMatch(result)) + try { - return result; - } + var result = action(context, cancellationToken); - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + if (!shouldRetryResultPredicates.AnyMatch(result)) + { + return result; + } + + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); - if (!canRetry) - { - return result; - } + if (!canRetry) + { + return result; + } - outcome = new DelegateResult(result); - } - catch (Exception ex) - { - var handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); - if (handledException == null) - { - throw; + outcome = new DelegateResult(result); } + catch (Exception ex) + { + var handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); + if (handledException == null) + { + throw; + } - canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); + canRetry = tryCount < permittedRetryCount && (sleepDurationsEnumerable == null || sleepDurationsEnumerator.MoveNext()); - if (!canRetry) - { - handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); - throw; - } + if (!canRetry) + { + handledException.RethrowWithOriginalStackTraceIfDiffersFrom(ex); + throw; + } - outcome = new DelegateResult(handledException); - } + outcome = new DelegateResult(handledException); + } - if (tryCount < int.MaxValue) { tryCount++; } + if (tryCount < int.MaxValue) { tryCount++; } - var waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); + var waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); - onRetry(outcome, waitDuration, tryCount, context); + onRetry(outcome, waitDuration, tryCount, context); - if (waitDuration > TimeSpan.Zero) - { - SystemClock.Sleep(waitDuration, cancellationToken); + if (waitDuration > TimeSpan.Zero) + { + SystemClock.Sleep(waitDuration, cancellationToken); + } } - } - } - finally - { - sleepDurationsEnumerator?.Dispose(); + } + finally + { + sleepDurationsEnumerator?.Dispose(); + } } } -} \ No newline at end of file +} diff --git a/src/Polly/Retry/RetryPolicy.cs b/src/Polly/Retry/RetryPolicy.cs index 7d1216df1ef..6a5367dee1f 100644 --- a/src/Polly/Retry/RetryPolicy.cs +++ b/src/Polly/Retry/RetryPolicy.cs @@ -3,87 +3,88 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Retry; - -/// -/// A retry policy that can be applied to synchronous delegates. -/// -public class RetryPolicy : Policy, IRetryPolicy +namespace Polly.Retry { - private readonly Action _onRetry; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func _sleepDurationProvider; - - internal RetryPolicy( - PolicyBuilder policyBuilder, - Action onRetry, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func sleepDurationProvider = null - ) - : base(policyBuilder) + /// + /// A retry policy that can be applied to synchronous delegates. + /// + public class RetryPolicy : Policy, IRetryPolicy { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); - } + private readonly Action _onRetry; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func _sleepDurationProvider; - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RetryEngine.Implementation( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates.None, - (outcome, timespan, retryCount, ctx) => _onRetry(outcome.Exception, timespan, retryCount, ctx), - _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider != null - ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) - : (Func, Context, TimeSpan>)null - ); -} + internal RetryPolicy( + PolicyBuilder policyBuilder, + Action onRetry, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func sleepDurationProvider = null + ) + : base(policyBuilder) + { + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); + } -/// -/// A retry policy that can be applied to synchronous delegates returning a value of type . -/// -public class RetryPolicy : Policy, IRetryPolicy -{ - private readonly Action, TimeSpan, int, Context> _onRetry; - private readonly int _permittedRetryCount; - private readonly IEnumerable _sleepDurationsEnumerable; - private readonly Func, Context, TimeSpan> _sleepDurationProvider; + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RetryEngine.Implementation( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + (outcome, timespan, retryCount, ctx) => _onRetry(outcome.Exception, timespan, retryCount, ctx), + _permittedRetryCount, + _sleepDurationsEnumerable, + _sleepDurationProvider != null + ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx) + : (Func, Context, TimeSpan>)null + ); + } - internal RetryPolicy( - PolicyBuilder policyBuilder, - Action, TimeSpan, int, Context> onRetry, - int permittedRetryCount = Int32.MaxValue, - IEnumerable sleepDurationsEnumerable = null, - Func, Context, TimeSpan> sleepDurationProvider = null - ) - : base(policyBuilder) + /// + /// A retry policy that can be applied to synchronous delegates returning a value of type . + /// + public class RetryPolicy : Policy, IRetryPolicy { - _permittedRetryCount = permittedRetryCount; - _sleepDurationsEnumerable = sleepDurationsEnumerable; - _sleepDurationProvider = sleepDurationProvider; - _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); - } + private readonly Action, TimeSpan, int, Context> _onRetry; + private readonly int _permittedRetryCount; + private readonly IEnumerable _sleepDurationsEnumerable; + private readonly Func, Context, TimeSpan> _sleepDurationProvider; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => RetryEngine.Implementation( - action, - context, - cancellationToken, - ExceptionPredicates, - ResultPredicates, - _onRetry, - _permittedRetryCount, - _sleepDurationsEnumerable, - _sleepDurationProvider - ); + internal RetryPolicy( + PolicyBuilder policyBuilder, + Action, TimeSpan, int, Context> onRetry, + int permittedRetryCount = Int32.MaxValue, + IEnumerable sleepDurationsEnumerable = null, + Func, Context, TimeSpan> sleepDurationProvider = null + ) + : base(policyBuilder) + { + _permittedRetryCount = permittedRetryCount; + _sleepDurationsEnumerable = sleepDurationsEnumerable; + _sleepDurationProvider = sleepDurationProvider; + _onRetry = onRetry ?? throw new ArgumentNullException(nameof(onRetry)); + } + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => RetryEngine.Implementation( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates, + _onRetry, + _permittedRetryCount, + _sleepDurationsEnumerable, + _sleepDurationProvider + ); + } } \ No newline at end of file diff --git a/src/Polly/Retry/RetrySyntax.cs b/src/Polly/Retry/RetrySyntax.cs index 03bdf95f869..48fef596e28 100644 --- a/src/Polly/Retry/RetrySyntax.cs +++ b/src/Polly/Retry/RetrySyntax.cs @@ -3,631 +3,632 @@ using System.Linq; using Polly.Retry; -namespace Polly; - -/// -/// Fluent API for defining a Retry . -/// -public static class RetrySyntax +namespace Polly { /// - /// Builds a that will retry once. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder) - => policyBuilder.Retry(1); - - /// - /// Builds a that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) - { - Action doNothing = (_, _) => { }; - - return policyBuilder.Retry(retryCount, doNothing); - } - - /// - /// Builds a that will retry once - /// calling on retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry once - /// calling on retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx), - retryCount); - } - - /// - /// Builds a that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) - { - Action doNothing = _ => { }; - - return policyBuilder.RetryForever(doNothing); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((Exception outcome, Context _) => onRetry(outcome)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetry(outcome, ctx) - ); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the raised exception, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx) - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, _) => onRetry(outcome, span) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - var sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); - - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetry( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetry(sleepDurations, doNothing); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the raised exception and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the raised exception, current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the raised exception, current sleep duration, retry count and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - onRetry, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetry(exception, timespan) - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _, _) => sleepDurationProvider(retryCount), - (exception, i, timespan, _) => onRetry(exception, i, timespan) - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the raised exception, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// Fluent API for defining a Retry . /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + public static class RetrySyntax { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), - sleepDurationProvider: sleepDurationProvider - ); + /// + /// Builds a that will retry once. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder) + => policyBuilder.Retry(1); + + /// + /// Builds a that will retry times. + /// + /// The policy builder. + /// The retry count. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) + { + Action doNothing = (_, _) => { }; + + return policyBuilder.Retry(retryCount, doNothing); + } + + /// + /// Builds a that will retry once + /// calling on retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.Retry(1, onRetry); + + /// + /// Builds a that will retry times + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry once + /// calling on retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action onRetry) + => policyBuilder.Retry(1, onRetry); + + /// + /// Builds a that will retry times + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx), + retryCount); + } + + /// + /// Builds a that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) + { + Action doNothing = _ => { }; + + return policyBuilder.RetryForever(doNothing); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((Exception outcome, Context _) => onRetry(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, _, ctx) => onRetry(outcome, ctx) + ); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx) + ); + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, _) => onRetry(outcome, span) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + var sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new RetryPolicy( + policyBuilder, + onRetry, + retryCount, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + ); + } + + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetry( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + onRetry, + retryCount, + sleepDurationProvider: sleepDurationProvider + ); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetry(sleepDurations, doNothing); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the raised exception and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the raised exception, current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the raised exception, current sleep duration, retry count and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action onRetry) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + onRetry, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetry(exception, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _, _) => sleepDurationProvider(retryCount), + (exception, i, timespan, _) => onRetry(exception, i, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous exception and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider + ); + } } } \ No newline at end of file diff --git a/src/Polly/Retry/RetryTResultSyntax.cs b/src/Polly/Retry/RetryTResultSyntax.cs index 5870c70859b..25e599b40d1 100644 --- a/src/Polly/Retry/RetryTResultSyntax.cs +++ b/src/Polly/Retry/RetryTResultSyntax.cs @@ -3,672 +3,673 @@ using Polly.Retry; using System.Linq; -namespace Polly; - -/// -/// Fluent API for defining a Retry . -/// -public static class RetryTResultSyntax +namespace Polly { /// - /// Builds a that will retry once. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder) - => policyBuilder.Retry(1); - - /// - /// Builds a that will retry times. - /// - /// The policy builder. - /// The retry count. - /// The policy instance. - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) - { - Action, int> doNothing = (_, _) => { }; - - return policyBuilder.Retry(retryCount, doNothing); - } - - /// - /// Builds a that will retry once - /// calling on retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int> onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry once - /// calling on retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - => policyBuilder.Retry(1, onRetry); - - /// - /// Builds a that will retry times - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The retry count. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// onRetry - public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx), - retryCount); - } - - /// - /// Builds a that will retry indefinitely until the action succeeds. - /// - /// The policy builder. - /// The policy instance. - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) - { - Action> doNothing = _ => { }; - - return policyBuilder.RetryForever(doNothing); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((DelegateResult outcome, Context _) => onRetry(outcome)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result and retry count. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, _, ctx) => onRetry(outcome, ctx) - ); - } - - /// - /// Builds a that will retry indefinitely - /// calling on each retry with the handled exception or result, retry count and context data. - /// - /// The policy builder. - /// The action to call on each retry. - /// The policy instance. - /// onRetry - public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, _, i, ctx) => onRetry(outcome, i, ctx) - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, _) => onRetry(outcome, span) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc). - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - var sleepDurations = Enumerable.Range(1, retryCount) - .Select(sleepDurationProvider); - - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) - { - Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - => policyBuilder.WaitAndRetry( - retryCount, - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry - ); - - /// - /// Builds a that will wait and retry times. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider) - { - Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; - - return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// sleepDurationProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry( - retryCount, - sleepDurationProvider, - (outcome, span, _, ctx) => onRetry(outcome, span, ctx) - ); - } - - /// - /// Builds a that will wait and retry times - /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// The retry count. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The action to call on each retry. - /// The policy instance. - /// retryCount;Value must be greater than or equal to zero. - /// - /// timeSpanProvider - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - { - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - onRetry, - retryCount, - sleepDurationProvider: sleepDurationProvider - ); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The policy instance. - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) - { - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetry(sleepDurations, doNothing); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the handled exception or result and the current sleep duration. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the handled exception or result, current sleep duration and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) - { - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); - } - - /// - /// Builds a that will wait and retry as many times as there are provided - /// calling on each retry with the handled exception or result, current sleep duration, retry count and context data. - /// On each retry, the duration to wait is the current item. - /// - /// The policy builder. - /// The sleep durations to wait for on each retry. - /// The action to call on each retry. - /// The policy instance. - /// - /// sleepDurations - /// or - /// onRetry - /// - public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) - { - if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - onRetry, - sleepDurationsEnumerable: sleepDurations - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan> doNothing = (_, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context - /// - /// The policy builder. - /// The function that provides the duration to wait for for a particular retry attempt. - /// The policy instance. - /// sleepDurationProvider - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - - Action, TimeSpan, Context> doNothing = (_, _, _) => { }; - - return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _) => sleepDurationProvider(retryCount), - (exception, timespan, _) => onRetry(exception, timespan) - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and retry count. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return policyBuilder.WaitAndRetryForever( - (retryCount, _, _) => sleepDurationProvider(retryCount), - (outcome, i, timespan, _) => onRetry(outcome, i, timespan) - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc) and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - return policyBuilder.WaitAndRetryForever( - (i, _, ctx) => sleepDurationProvider(i, ctx), - onRetry - ); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. - /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) - { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), - sleepDurationProvider: sleepDurationProvider); - } - - /// - /// Builds a that will wait and retry indefinitely until the action succeeds, - /// calling on each retry with the handled exception or result, retry count and execution context. - /// On each retry, the duration to wait is calculated by calling with - /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// Fluent API for defining a Retry . /// - /// The policy builder. - /// A function providing the duration to wait before retrying. - /// The action to call on each retry. - /// The policy instance. - /// sleepDurationProvider - /// onRetry - public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + public static class RetryTResultSyntax { - if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - return new RetryPolicy( - policyBuilder, - (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), - sleepDurationProvider: sleepDurationProvider - ); + /// + /// Builds a that will retry once. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder) + => policyBuilder.Retry(1); + + /// + /// Builds a that will retry times. + /// + /// The policy builder. + /// The retry count. + /// The policy instance. + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount) + { + Action, int> doNothing = (_, _) => { }; + + return policyBuilder.Retry(retryCount, doNothing); + } + + /// + /// Builds a that will retry once + /// calling on retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int> onRetry) + => policyBuilder.Retry(1, onRetry); + + /// + /// Builds a that will retry times + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry once + /// calling on retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + => policyBuilder.Retry(1, onRetry); + + /// + /// Builds a that will retry times + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The retry count. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// onRetry + public static RetryPolicy Retry(this PolicyBuilder policyBuilder, int retryCount, Action, int, Context> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx), + retryCount); + } + + /// + /// Builds a that will retry indefinitely until the action succeeds. + /// + /// The policy builder. + /// The policy instance. + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder) + { + Action> doNothing = _ => { }; + + return policyBuilder.RetryForever(doNothing); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((DelegateResult outcome, Context _) => onRetry(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((outcome, i, _) => onRetry(outcome, i)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, _, ctx) => onRetry(outcome, ctx) + ); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, _, i, ctx) => onRetry(outcome, i, ctx) + ); + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, _) => onRetry(outcome, span) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + var sleepDurations = Enumerable.Range(1, retryCount) + .Select(sleepDurationProvider); + + return new RetryPolicy( + policyBuilder, + onRetry, + retryCount, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + => policyBuilder.WaitAndRetry( + retryCount, + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + + /// + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider) + { + Action, TimeSpan, int, Context> doNothing = (_, _, _, _) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, _, ctx) => onRetry(outcome, span, ctx) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + onRetry, + retryCount, + sleepDurationProvider: sleepDurationProvider + ); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations) + { + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetry(sleepDurations, doNothing); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the handled exception or result and the current sleep duration. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, _) => onRetry(outcome, span)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry(sleepDurations, (outcome, span, _, ctx) => onRetry(outcome, span, ctx)); + } + + /// + /// Builds a that will wait and retry as many times as there are provided + /// calling on each retry with the handled exception or result, current sleep duration, retry count and context data. + /// On each retry, the duration to wait is the current item. + /// + /// The policy builder. + /// The sleep durations to wait for on each retry. + /// The action to call on each retry. + /// The policy instance. + /// + /// sleepDurations + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, IEnumerable sleepDurations, Action, TimeSpan, int, Context> onRetry) + { + if (sleepDurations == null) throw new ArgumentNullException(nameof(sleepDurations)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + onRetry, + sleepDurationsEnumerable: sleepDurations + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan> doNothing = (_, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context + /// + /// The policy builder. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + /// sleepDurationProvider + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + + Action, TimeSpan, Context> doNothing = (_, _, _) => { }; + + return policyBuilder.WaitAndRetryForever(sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _) => sleepDurationProvider(retryCount), + (exception, timespan, _) => onRetry(exception, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and retry count. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, _, _) => sleepDurationProvider(retryCount), + (outcome, i, timespan, _) => onRetry(outcome, i, timespan) + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc) and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, _, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (outcome, timespan, _, ctx) => onRetry(outcome, timespan, ctx), + sleepDurationProvider: sleepDurationProvider); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and execution context. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry number (1 for first retry, 2 for second etc), previous execution result and execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + policyBuilder, + (exception, timespan, i, ctx) => onRetry(exception, i, timespan, ctx), + sleepDurationProvider: sleepDurationProvider + ); + } } } \ No newline at end of file diff --git a/src/Polly/Timeout/AsyncTimeoutEngine.cs b/src/Polly/Timeout/AsyncTimeoutEngine.cs index a75f9a1a1c4..1f492cb47a4 100644 --- a/src/Polly/Timeout/AsyncTimeoutEngine.cs +++ b/src/Polly/Timeout/AsyncTimeoutEngine.cs @@ -3,77 +3,78 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Timeout; - -internal static class AsyncTimeoutEngine +namespace Polly.Timeout { - internal static async Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync, - bool continueOnCapturedContext) + internal static class AsyncTimeoutEngine { - cancellationToken.ThrowIfCancellationRequested(); - var timeout = timeoutProvider(context); - - using (var timeoutCancellationTokenSource = new CancellationTokenSource()) + internal static async Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync, + bool continueOnCapturedContext) { - using (var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) - { - Task actionTask = null; - var combinedToken = combinedTokenSource.Token; + cancellationToken.ThrowIfCancellationRequested(); + var timeout = timeoutProvider(context); - try + using (var timeoutCancellationTokenSource = new CancellationTokenSource()) + { + using (var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { - if (timeoutStrategy == TimeoutStrategy.Optimistic) + Task actionTask = null; + var combinedToken = combinedTokenSource.Token; + + try { - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - return await action(context, combinedToken).ConfigureAwait(continueOnCapturedContext); - } + if (timeoutStrategy == TimeoutStrategy.Optimistic) + { + SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); + return await action(context, combinedToken).ConfigureAwait(continueOnCapturedContext); + } - // else: timeoutStrategy == TimeoutStrategy.Pessimistic + // else: timeoutStrategy == TimeoutStrategy.Pessimistic - var timeoutTask = timeoutCancellationTokenSource.Token.AsTask(); + var timeoutTask = timeoutCancellationTokenSource.Token.AsTask(); - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); + SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - actionTask = action(context, combinedToken); + actionTask = action(context, combinedToken); - return await (await Task.WhenAny(actionTask, timeoutTask).ConfigureAwait(continueOnCapturedContext)).ConfigureAwait(continueOnCapturedContext); + return await (await Task.WhenAny(actionTask, timeoutTask).ConfigureAwait(continueOnCapturedContext)).ConfigureAwait(continueOnCapturedContext); - } - catch (Exception ex) - { - // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) - // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. - if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) - { - await onTimeoutAsync(context, timeout, actionTask, ex).ConfigureAwait(continueOnCapturedContext); - throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", ex); } + catch (Exception ex) + { + // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) + // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. + if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) + { + await onTimeoutAsync(context, timeout, actionTask, ex).ConfigureAwait(continueOnCapturedContext); + throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", ex); + } - throw; + throw; + } } } } - } - private static Task AsTask(this CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); + private static Task AsTask(this CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); - // A generalised version of this method would include a hotpath returning a canceled task (rather than setting up a registration) if (cancellationToken.IsCancellationRequested) on entry. This is omitted, since we only start the timeout countdown in the token _after calling this method. + // A generalised version of this method would include a hotpath returning a canceled task (rather than setting up a registration) if (cancellationToken.IsCancellationRequested) on entry. This is omitted, since we only start the timeout countdown in the token _after calling this method. - IDisposable registration = null; - registration = cancellationToken.Register(() => - { - tcs.TrySetCanceled(); - registration?.Dispose(); - }, useSynchronizationContext: false); + IDisposable registration = null; + registration = cancellationToken.Register(() => + { + tcs.TrySetCanceled(); + registration?.Dispose(); + }, useSynchronizationContext: false); - return tcs.Task; + return tcs.Task; + } } -} \ No newline at end of file +} diff --git a/src/Polly/Timeout/AsyncTimeoutPolicy.cs b/src/Polly/Timeout/AsyncTimeoutPolicy.cs index 0f810a074cd..d139b9c4a07 100644 --- a/src/Polly/Timeout/AsyncTimeoutPolicy.cs +++ b/src/Polly/Timeout/AsyncTimeoutPolicy.cs @@ -3,80 +3,81 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Timeout; - -/// -/// A timeout policy which can be applied to async delegates. -/// -public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy +namespace Polly.Timeout { - private readonly Func _timeoutProvider; - private readonly TimeoutStrategy _timeoutStrategy; - private readonly Func _onTimeoutAsync; - - internal AsyncTimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync - ) + /// + /// A timeout policy which can be applied to async delegates. + /// + public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); + private readonly Func _timeoutProvider; + private readonly TimeoutStrategy _timeoutStrategy; + private readonly Func _onTimeoutAsync; + + internal AsyncTimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync + ) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); + } + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + { + return AsyncTimeoutEngine.ImplementationAsync( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeoutAsync, + continueOnCapturedContext); + } } - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) + /// + /// A timeout policy which can be applied to async delegates. + /// + /// The return type of delegates which may be executed through the policy. + public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy { - return AsyncTimeoutEngine.ImplementationAsync( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeoutAsync, - continueOnCapturedContext); - } -} + private Func _timeoutProvider; + private TimeoutStrategy _timeoutStrategy; + private Func _onTimeoutAsync; -/// -/// A timeout policy which can be applied to async delegates. -/// -/// The return type of delegates which may be executed through the policy. -public class AsyncTimeoutPolicy : AsyncPolicy, ITimeoutPolicy -{ - private Func _timeoutProvider; - private TimeoutStrategy _timeoutStrategy; - private Func _onTimeoutAsync; + internal AsyncTimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); + } - internal AsyncTimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync) - { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeoutAsync = onTimeoutAsync ?? throw new ArgumentNullException(nameof(onTimeoutAsync)); + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( + Func> action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncTimeoutEngine.ImplementationAsync( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeoutAsync, + continueOnCapturedContext); } - - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( - Func> action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncTimeoutEngine.ImplementationAsync( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeoutAsync, - continueOnCapturedContext); } \ No newline at end of file diff --git a/src/Polly/Timeout/AsyncTimeoutSyntax.cs b/src/Polly/Timeout/AsyncTimeoutSyntax.cs index b37ed69c0e7..f8babf653dc 100644 --- a/src/Polly/Timeout/AsyncTimeoutSyntax.cs +++ b/src/Polly/Timeout/AsyncTimeoutSyntax.cs @@ -3,388 +3,389 @@ using Polly.Timeout; using Polly.Utilities; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// The policy instance. - /// seconds;Value must be greater than zero. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// seconds;Value must be greater than zero. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// seconds;Value must be greater than zero. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; - - return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync) + public partial class Policy { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return new AsyncTimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeoutAsync - ); + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// The policy instance. + /// seconds;Value must be greater than zero. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// seconds;Value must be greater than zero. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// seconds;Value must be greater than zero. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Func doNothingAsync = (_, _, _, _) => TaskHelper.EmptyTask; + + return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return new AsyncTimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeoutAsync + ); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs b/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs index ce047e7d75a..edff08a0ad3 100644 --- a/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs +++ b/src/Polly/Timeout/AsyncTimeoutTResultSyntax.cs @@ -2,382 +2,383 @@ using System.Threading.Tasks; using Polly.Timeout; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) - { - if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); - - return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) - { - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// timeoutProvider - /// The policy instance. - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); - return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) - => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) - { - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); - } - - /// - /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeoutAsync - public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + public partial class Policy { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - - return new AsyncTimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeoutAsync - ); + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeout, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(_ => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + + return TimeoutAsync(_ => timeout, timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(_ => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider) + { + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// timeoutProvider + /// The policy instance. + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Func doNothingAsync = (_, _, _, _) => Task.FromResult(default(TResult)); + return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + => TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeoutAsync(ctx, timeout, task)); + } + + /// + /// Builds an that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static AsyncTimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return new AsyncTimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeoutAsync + ); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Timeout/ITimeoutPolicy.cs b/src/Polly/Timeout/ITimeoutPolicy.cs index 720fc1f9053..bdf26a02be4 100644 --- a/src/Polly/Timeout/ITimeoutPolicy.cs +++ b/src/Polly/Timeout/ITimeoutPolicy.cs @@ -1,16 +1,18 @@ -namespace Polly.Timeout; - -/// -/// Defines properties and methods common to all Timeout policies. -/// -public interface ITimeoutPolicy : IsPolicy +namespace Polly.Timeout { -} + /// + /// Defines properties and methods common to all Timeout policies. + /// -/// -/// Defines properties and methods common to all Timeout policies generic-typed for executions returning results of type . -/// -public interface ITimeoutPolicy : ITimeoutPolicy -{ + public interface ITimeoutPolicy : IsPolicy + { + } -} \ No newline at end of file + /// + /// Defines properties and methods common to all Timeout policies generic-typed for executions returning results of type . + /// + public interface ITimeoutPolicy : ITimeoutPolicy + { + + } +} diff --git a/src/Polly/Timeout/TimeoutEngine.cs b/src/Polly/Timeout/TimeoutEngine.cs index 9c988355367..e236c22e6b6 100644 --- a/src/Polly/Timeout/TimeoutEngine.cs +++ b/src/Polly/Timeout/TimeoutEngine.cs @@ -4,68 +4,69 @@ using System.Threading.Tasks; using Polly.Utilities; -namespace Polly.Timeout; - -internal static class TimeoutEngine +namespace Polly.Timeout { - internal static TResult Implementation( - Func action, - Context context, - CancellationToken cancellationToken, - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) + internal static class TimeoutEngine { - cancellationToken.ThrowIfCancellationRequested(); - var timeout = timeoutProvider(context); - - using (var timeoutCancellationTokenSource = new CancellationTokenSource()) + internal static TResult Implementation( + Func action, + Context context, + CancellationToken cancellationToken, + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) { - using (var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) - { - var combinedToken = combinedTokenSource.Token; + cancellationToken.ThrowIfCancellationRequested(); + var timeout = timeoutProvider(context); - Task actionTask = null; - try + using (var timeoutCancellationTokenSource = new CancellationTokenSource()) + { + using (var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { - if (timeoutStrategy == TimeoutStrategy.Optimistic) + var combinedToken = combinedTokenSource.Token; + + Task actionTask = null; + try { - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - return action(context, combinedToken); - } + if (timeoutStrategy == TimeoutStrategy.Optimistic) + { + SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); + return action(context, combinedToken); + } - // else: timeoutStrategy == TimeoutStrategy.Pessimistic + // else: timeoutStrategy == TimeoutStrategy.Pessimistic - SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); + SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); - actionTask = Task.Run(() => + actionTask = Task.Run(() => action(context, combinedToken) // cancellation token here allows the user delegate to react to cancellation: possibly clear up; then throw an OperationCanceledException. - , combinedToken); // cancellation token here only allows Task.Run() to not begin the passed delegate at all, if cancellation occurs prior to invoking the delegate. - try - { - actionTask.Wait(timeoutCancellationTokenSource.Token); // cancellation token here cancels the Wait() and causes it to throw, but does not cancel actionTask. We use only timeoutCancellationTokenSource.Token here, not combinedToken. If we allowed the user's cancellation token to cancel the Wait(), in this pessimistic scenario where the user delegate may not observe that cancellation, that would create a no-longer-observed task. That task could in turn later fault before completing, risking an UnobservedTaskException. + , combinedToken); // cancellation token here only allows Task.Run() to not begin the passed delegate at all, if cancellation occurs prior to invoking the delegate. + try + { + actionTask.Wait(timeoutCancellationTokenSource.Token); // cancellation token here cancels the Wait() and causes it to throw, but does not cancel actionTask. We use only timeoutCancellationTokenSource.Token here, not combinedToken. If we allowed the user's cancellation token to cancel the Wait(), in this pessimistic scenario where the user delegate may not observe that cancellation, that would create a no-longer-observed task. That task could in turn later fault before completing, risking an UnobservedTaskException. + } + catch (AggregateException ex) when (ex.InnerExceptions.Count == 1) // Issue #270. Unwrap extra AggregateException caused by the way pessimistic timeout policy for synchronous executions is necessarily constructed. + { + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + } + + return actionTask.Result; } - catch (AggregateException ex) when (ex.InnerExceptions.Count == 1) // Issue #270. Unwrap extra AggregateException caused by the way pessimistic timeout policy for synchronous executions is necessarily constructed. + catch (Exception ex) { - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); - } + // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) + // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. + if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) + { + onTimeout(context, timeout, actionTask, ex); + throw new TimeoutRejectedException("The delegate executed through TimeoutPolicy did not complete within the timeout.", ex); + } - return actionTask.Result; - } - catch (Exception ex) - { - // Note that we cannot rely on testing (operationCanceledException.CancellationToken == combinedToken || operationCanceledException.CancellationToken == timeoutCancellationTokenSource.Token) - // as either of those tokens could have been onward combined with another token by executed code, and so may not be the token expressed on operationCanceledException.CancellationToken. - if (ex is OperationCanceledException && timeoutCancellationTokenSource.IsCancellationRequested) - { - onTimeout(context, timeout, actionTask, ex); - throw new TimeoutRejectedException("The delegate executed through TimeoutPolicy did not complete within the timeout.", ex); + throw; } - - throw; } } } - } + } } \ No newline at end of file diff --git a/src/Polly/Timeout/TimeoutPolicy.cs b/src/Polly/Timeout/TimeoutPolicy.cs index c127f13c291..9b3b0470f10 100644 --- a/src/Polly/Timeout/TimeoutPolicy.cs +++ b/src/Polly/Timeout/TimeoutPolicy.cs @@ -3,65 +3,66 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Timeout; - -/// -/// A timeout policy which can be applied to delegates. -/// -public class TimeoutPolicy : Policy, ITimeoutPolicy +namespace Polly.Timeout { - private Func _timeoutProvider; - private TimeoutStrategy _timeoutStrategy; - private Action _onTimeout; - - internal TimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) + /// + /// A timeout policy which can be applied to delegates. + /// + public class TimeoutPolicy : Policy, ITimeoutPolicy { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); - } + private Func _timeoutProvider; + private TimeoutStrategy _timeoutStrategy; + private Action _onTimeout; - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => TimeoutEngine.Implementation( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeout); -} + internal TimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); + } -/// -/// A timeout policy which can be applied to delegates returning a value of type . -/// -public class TimeoutPolicy : Policy, ITimeoutPolicy -{ - private Func _timeoutProvider; - private TimeoutStrategy _timeoutStrategy; - private Action _onTimeout; + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => TimeoutEngine.Implementation( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeout); + } - internal TimeoutPolicy( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) + /// + /// A timeout policy which can be applied to delegates returning a value of type . + /// + public class TimeoutPolicy : Policy, ITimeoutPolicy { - _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); - _timeoutStrategy = timeoutStrategy; - _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); - } + private Func _timeoutProvider; + private TimeoutStrategy _timeoutStrategy; + private Action _onTimeout; - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => TimeoutEngine.Implementation( - action, - context, - cancellationToken, - _timeoutProvider, - _timeoutStrategy, - _onTimeout); + internal TimeoutPolicy( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + _timeoutProvider = timeoutProvider ?? throw new ArgumentNullException(nameof(timeoutProvider)); + _timeoutStrategy = timeoutStrategy; + _onTimeout = onTimeout ?? throw new ArgumentNullException(nameof(onTimeout)); + } + + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => TimeoutEngine.Implementation( + action, + context, + cancellationToken, + _timeoutProvider, + _timeoutStrategy, + _onTimeout); + } } \ No newline at end of file diff --git a/src/Polly/Timeout/TimeoutStrategy.cs b/src/Polly/Timeout/TimeoutStrategy.cs index 905978ddf62..b948888fdc0 100644 --- a/src/Polly/Timeout/TimeoutStrategy.cs +++ b/src/Polly/Timeout/TimeoutStrategy.cs @@ -1,18 +1,19 @@ using System.Threading; -namespace Polly.Timeout; - -/// -/// Defines strategies used by s to enforce timeouts. -/// -public enum TimeoutStrategy +namespace Polly.Timeout { /// - /// An optimistic . The relies on a timing-out to cancel executed delegates by co-operative cancellation. - /// - Optimistic, - /// - /// An pessimistic . The will assume the delegates passed to be executed will not necessarily honor any timing-out , but the policy will still guarantee timing out (and returning to the caller) by other means. + /// Defines strategies used by s to enforce timeouts. /// - Pessimistic -} \ No newline at end of file + public enum TimeoutStrategy + { + /// + /// An optimistic . The relies on a timing-out to cancel executed delegates by co-operative cancellation. + /// + Optimistic, + /// + /// An pessimistic . The will assume the delegates passed to be executed will not necessarily honor any timing-out , but the policy will still guarantee timing out (and returning to the caller) by other means. + /// + Pessimistic + } +} diff --git a/src/Polly/Timeout/TimeoutSyntax.cs b/src/Polly/Timeout/TimeoutSyntax.cs index 8b4b07cce14..1b5b3b3c413 100644 --- a/src/Polly/Timeout/TimeoutSyntax.cs +++ b/src/Polly/Timeout/TimeoutSyntax.cs @@ -2,382 +2,383 @@ using System; using System.Threading.Tasks; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static TimeoutPolicy Timeout(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// The policy instance. - /// seconds;Value must be greater than zero. - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + public partial class Policy { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return new TimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeout); + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static TimeoutPolicy Timeout(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// The policy instance. + /// seconds;Value must be greater than zero. + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return new TimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeout); + } } } \ No newline at end of file diff --git a/src/Polly/Timeout/TimeoutTResultSyntax.cs b/src/Polly/Timeout/TimeoutTResultSyntax.cs index 5786392f4c6..94634fe0435 100644 --- a/src/Polly/Timeout/TimeoutTResultSyntax.cs +++ b/src/Polly/Timeout/TimeoutTResultSyntax.cs @@ -2,389 +2,390 @@ using Polly.Timeout; using System.Threading.Tasks; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// seconds;Value must be greater than zero. - /// The policy instance. - public static TimeoutPolicy Timeout(int seconds) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// The policy instance. - /// seconds;Value must be greater than zero. - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The number of seconds after which to timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateSecondsTimeout(seconds); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The number of seconds after which to timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// seconds;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - - return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The timeout. - /// The timeout strategy. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, _, _, _) => { }; - - return Timeout(_ => timeout, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The timeout. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// The timeout. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeout;Value must be greater than zero. - /// onTimeout - public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - TimeoutValidator.ValidateTimeSpanTimeout(timeout); - return Timeout(_ => timeout, timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - Action doNothing = (_, _, _, _) => { }; - return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + public partial class Policy { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - - return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// timeoutProvider - /// The policy instance. - public static TimeoutPolicy Timeout(Func timeoutProvider) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// The policy instance. - /// timeoutProvider - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) - { - Action doNothing = (_, _, _, _) => { }; - return Timeout(timeoutProvider, timeoutStrategy, doNothing); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// A function to provide the timeout for this execution. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) - => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) - { - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); - } - - /// - /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. - /// - /// The return type of delegates which may be executed through the policy. - /// A function to provide the timeout for this execution. - /// The timeout strategy. - /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . - /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. - /// The policy instance. - /// timeoutProvider - /// onTimeout - public static TimeoutPolicy Timeout( - Func timeoutProvider, - TimeoutStrategy timeoutStrategy, - Action onTimeout) - { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - - return new TimeoutPolicy( - timeoutProvider, - timeoutStrategy, - onTimeout); + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// seconds;Value must be greater than zero. + /// The policy instance. + public static TimeoutPolicy Timeout(int seconds) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// The policy instance. + /// seconds;Value must be greater than zero. + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + return Timeout(_ => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateSecondsTimeout(seconds); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(_ => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The timeout. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + Action doNothing = (_, _, _, _) => { }; + + return Timeout(_ => timeout, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout) + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(_ => timeout, timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + Action doNothing = (_, _, _, _) => { }; + return Timeout(_ => timeoutProvider(), timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(_ => timeoutProvider(), timeoutStrategy, onTimeout); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// timeoutProvider + /// The policy instance. + public static TimeoutPolicy Timeout(Func timeoutProvider) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// The policy instance. + /// timeoutProvider + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) + { + Action doNothing = (_, _, _, _) => { }; + return Timeout(timeoutProvider, timeoutStrategy, doNothing); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + => Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, and a capturing the abandoned, timed-out action. + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, _) => onTimeout(ctx, timeout, task)); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded cooperatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout( + Func timeoutProvider, + TimeoutStrategy timeoutStrategy, + Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + + return new TimeoutPolicy( + timeoutProvider, + timeoutStrategy, + onTimeout); + } } } \ No newline at end of file diff --git a/src/Polly/Timeout/TimeoutValidator.cs b/src/Polly/Timeout/TimeoutValidator.cs index 9d5616618b0..4175fee970c 100644 --- a/src/Polly/Timeout/TimeoutValidator.cs +++ b/src/Polly/Timeout/TimeoutValidator.cs @@ -1,19 +1,20 @@ using System; -namespace Polly.Timeout; - -internal static class TimeoutValidator +namespace Polly.Timeout { - internal static void ValidateSecondsTimeout(int seconds) + internal static class TimeoutValidator { - if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); - } + internal static void ValidateSecondsTimeout(int seconds) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + } - internal static void ValidateTimeSpanTimeout(TimeSpan timeout) - { - if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) - throw new ArgumentOutOfRangeException(nameof(timeout), timeout, - $"{nameof(timeout)} must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout)"); - } + internal static void ValidateTimeSpanTimeout(TimeSpan timeout) + { + if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) + throw new ArgumentOutOfRangeException(nameof(timeout), timeout, + $"{nameof(timeout)} must be a positive TimeSpan (or Timeout.InfiniteTimeSpan to indicate no timeout)"); + } -} \ No newline at end of file + } +} diff --git a/src/Polly/Utilities/EmptyStruct.cs b/src/Polly/Utilities/EmptyStruct.cs index 9fc8baac2e4..edc77510f76 100644 --- a/src/Polly/Utilities/EmptyStruct.cs +++ b/src/Polly/Utilities/EmptyStruct.cs @@ -1,9 +1,10 @@ -namespace Polly.Utilities; - -/// -/// A null struct for policies and actions which do not return a TResult. -/// -internal struct EmptyStruct +namespace Polly.Utilities { - internal static readonly EmptyStruct Instance = new EmptyStruct(); -} \ No newline at end of file + /// + /// A null struct for policies and actions which do not return a TResult. + /// + internal struct EmptyStruct + { + internal static readonly EmptyStruct Instance = new EmptyStruct(); + } +} diff --git a/src/Polly/Utilities/ExceptionExtensions.cs b/src/Polly/Utilities/ExceptionExtensions.cs index 0ac824e7a29..037f9845087 100644 --- a/src/Polly/Utilities/ExceptionExtensions.cs +++ b/src/Polly/Utilities/ExceptionExtensions.cs @@ -1,23 +1,24 @@ using System; using System.Runtime.ExceptionServices; -namespace Polly.Utilities; - -/// -/// Contains extension methods on the class. -/// -public static class ExceptionExtensions +namespace Polly.Utilities { /// - /// Rethrows the extended , , using the class to rethrow it with its original stack trace, if differs from . + /// Contains extension methods on the class. /// - /// The exception to throw, if it differs from - /// The exception to compare against. - public static void RethrowWithOriginalStackTraceIfDiffersFrom(this Exception exceptionPossiblyToThrow, Exception exceptionToCompare) + public static class ExceptionExtensions { - if (exceptionPossiblyToThrow != exceptionToCompare) + /// + /// Rethrows the extended , , using the class to rethrow it with its original stack trace, if differs from . + /// + /// The exception to throw, if it differs from + /// The exception to compare against. + public static void RethrowWithOriginalStackTraceIfDiffersFrom(this Exception exceptionPossiblyToThrow, Exception exceptionToCompare) { - ExceptionDispatchInfo.Capture(exceptionPossiblyToThrow).Throw(); + if (exceptionPossiblyToThrow != exceptionToCompare) + { + ExceptionDispatchInfo.Capture(exceptionPossiblyToThrow).Throw(); + } } } -} \ No newline at end of file +} diff --git a/src/Polly/Utilities/KeyHelper.cs b/src/Polly/Utilities/KeyHelper.cs index a93617ef8e6..1aff004fb76 100644 --- a/src/Polly/Utilities/KeyHelper.cs +++ b/src/Polly/Utilities/KeyHelper.cs @@ -1,8 +1,9 @@ using System; -namespace Polly.Utilities; - -internal static class KeyHelper +namespace Polly.Utilities { - public static string GuidPart() => Guid.NewGuid().ToString().Substring(0, 8); -} \ No newline at end of file + internal static class KeyHelper + { + public static string GuidPart() => Guid.NewGuid().ToString().Substring(0, 8); + } +} diff --git a/src/Polly/Utilities/SystemClock.cs b/src/Polly/Utilities/SystemClock.cs index 87b8b49656a..1f971455165 100644 --- a/src/Polly/Utilities/SystemClock.cs +++ b/src/Polly/Utilities/SystemClock.cs @@ -2,64 +2,65 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Utilities; - -/// -/// Time related delegates used to support different compilation targets and to improve testability of the code. -/// -public static class SystemClock +namespace Polly.Utilities { /// - /// Allows the setting of a custom Thread.Sleep implementation for testing. - /// By default this will use the 's . + /// Time related delegates used to support different compilation targets and to improve testability of the code. /// - public static Action Sleep = (timeSpan, cancellationToken) => + public static class SystemClock { - if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); - }; + /// + /// Allows the setting of a custom Thread.Sleep implementation for testing. + /// By default this will use the 's . + /// + public static Action Sleep = (timeSpan, cancellationToken) => + { + if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); + }; - /// - /// Allows the setting of a custom async Sleep implementation for testing. - /// By default this will be a call to taking a - /// - public static Func SleepAsync = Task.Delay; + /// + /// Allows the setting of a custom async Sleep implementation for testing. + /// By default this will be a call to taking a + /// + public static Func SleepAsync = Task.Delay; - /// - /// Allows the setting of a custom DateTime.UtcNow implementation for testing. - /// By default this will be a call to - /// - public static Func UtcNow = () => DateTime.UtcNow; + /// + /// Allows the setting of a custom DateTime.UtcNow implementation for testing. + /// By default this will be a call to + /// + public static Func UtcNow = () => DateTime.UtcNow; - /// - /// Allows the setting of a custom DateTimeOffset.UtcNow implementation for testing. - /// By default this will be a call to - /// - public static Func DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; + /// + /// Allows the setting of a custom DateTimeOffset.UtcNow implementation for testing. + /// By default this will be a call to + /// + public static Func DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; - /// - /// Allows the setting of a custom method for cancelling tokens after a timespan, for use in testing. - /// By default this will be a call to CancellationTokenSource.CancelAfter(timespan) - /// - public static Action CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); + /// + /// Allows the setting of a custom method for cancelling tokens after a timespan, for use in testing. + /// By default this will be a call to CancellationTokenSource.CancelAfter(timespan) + /// + public static Action CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); - /// - /// Resets the custom implementations to their defaults. - /// Should be called during test teardowns. - /// - public static void Reset() - { - Sleep = (timeSpan, cancellationToken) => + /// + /// Resets the custom implementations to their defaults. + /// Should be called during test teardowns. + /// + public static void Reset() { - if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); - }; + Sleep = (timeSpan, cancellationToken) => + { + if (cancellationToken.WaitHandle.WaitOne(timeSpan)) cancellationToken.ThrowIfCancellationRequested(); + }; - SleepAsync = Task.Delay; + SleepAsync = Task.Delay; - UtcNow = () => DateTime.UtcNow; + UtcNow = () => DateTime.UtcNow; - DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; + DateTimeOffsetUtcNow = () => DateTimeOffset.UtcNow; - CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); + CancelTokenAfter = (tokenSource, timespan) => tokenSource.CancelAfter(timespan); + } } } \ No newline at end of file diff --git a/src/Polly/Utilities/TaskHelper.cs b/src/Polly/Utilities/TaskHelper.cs index 0bc8eb3d007..be3cb06f8e4 100644 --- a/src/Polly/Utilities/TaskHelper.cs +++ b/src/Polly/Utilities/TaskHelper.cs @@ -1,20 +1,21 @@ using System.Threading.Tasks; -namespace Polly.Utilities; - -/// -/// Task helpers. -/// -public static class TaskHelper +namespace Polly.Utilities { /// - /// Defines a completed Task for use as a completed, empty asynchronous delegate. + /// Task helpers. /// - public static Task EmptyTask = + public static class TaskHelper + { + /// + /// Defines a completed Task for use as a completed, empty asynchronous delegate. + /// + public static Task EmptyTask = #if NETSTANDARD1_1 Task.FromResult(true) #else Task.CompletedTask #endif - ; -} \ No newline at end of file + ; + } +} diff --git a/src/Polly/Utilities/TimedLock.cs b/src/Polly/Utilities/TimedLock.cs index c00f69a49f3..d889979ee20 100644 --- a/src/Polly/Utilities/TimedLock.cs +++ b/src/Polly/Utilities/TimedLock.cs @@ -1,93 +1,95 @@ using System; using System.Threading; -namespace Polly.Utilities; -// Adapted from the link below, with slight modifications. - -// http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking -// Ian Griffiths (original TimedLock author) wrote: -// Thanks to Eric Gunnerson for recommending this be a struct rather -// than a class - avoids a heap allocation. -// Thanks to Change Gillespie and Jocelyn Coulmance for pointing out -// the bugs that then crept in when I changed it to use struct... -// Thanks to John Sands for providing the necessary incentive to make -// me invent a way of using a struct in both release and debug builds -// without losing the debug leak tracking. -internal struct TimedLock : IDisposable +namespace Polly.Utilities { - // The TimedLock class throws a LockTimeoutException if a lock cannot be obtained within the LockTimeout. This allows the easier discovery and debugging of deadlocks during Polly development, than if using a pure lock. - // We do not however ever want to throw a LockTimeoutException in production - hence the forked LockTimeout value below for DEBUG versus RELEASE builds. - // This applies particularly because CircuitBreakerPolicy runs state-change delegates during the lock, in order that the state change holds true (cannot be superseded by activity on other threads) while the delegate runs. + // Adapted from the link below, with slight modifications. + + // http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking + // Ian Griffiths (original TimedLock author) wrote: + // Thanks to Eric Gunnerson for recommending this be a struct rather + // than a class - avoids a heap allocation. + // Thanks to Change Gillespie and Jocelyn Coulmance for pointing out + // the bugs that then crept in when I changed it to use struct... + // Thanks to John Sands for providing the necessary incentive to make + // me invent a way of using a struct in both release and debug builds + // without losing the debug leak tracking. + internal struct TimedLock : IDisposable + { + // The TimedLock class throws a LockTimeoutException if a lock cannot be obtained within the LockTimeout. This allows the easier discovery and debugging of deadlocks during Polly development, than if using a pure lock. + // We do not however ever want to throw a LockTimeoutException in production - hence the forked LockTimeout value below for DEBUG versus RELEASE builds. + // This applies particularly because CircuitBreakerPolicy runs state-change delegates during the lock, in order that the state change holds true (cannot be superseded by activity on other threads) while the delegate runs. #if DEBUG - private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(5); #else private static readonly TimeSpan LockTimeout = TimeSpan.FromMilliseconds(int.MaxValue); #endif - public static TimedLock Lock(object o) - { - return Lock(o, LockTimeout); - } + public static TimedLock Lock(object o) + { + return Lock(o, LockTimeout); + } - private static TimedLock Lock(object o, TimeSpan timeout) - { - var tl = new TimedLock(o); - if (!Monitor.TryEnter(o, timeout)) + private static TimedLock Lock(object o, TimeSpan timeout) { + var tl = new TimedLock(o); + if (!Monitor.TryEnter(o, timeout)) + { #if DEBUG - GC.SuppressFinalize(tl.leakDetector); + GC.SuppressFinalize(tl.leakDetector); #endif - throw new LockTimeoutException(); - } + throw new LockTimeoutException(); + } - return tl; - } + return tl; + } - private TimedLock(object o) - { - target = o; + private TimedLock(object o) + { + target = o; #if DEBUG - leakDetector = new Sentinel(); + leakDetector = new Sentinel(); #endif - } - private object target; + } + private object target; - public void Dispose() - { - Monitor.Exit(target); + public void Dispose() + { + Monitor.Exit(target); - // It's a bad error if someone forgets to call Dispose, - // so in Debug builds, we put a finalizer in to detect - // the error. If Dispose is called, we suppress the - // finalizer. + // It's a bad error if someone forgets to call Dispose, + // so in Debug builds, we put a finalizer in to detect + // the error. If Dispose is called, we suppress the + // finalizer. #if DEBUG - GC.SuppressFinalize(leakDetector); + GC.SuppressFinalize(leakDetector); #endif - } + } #if DEBUG - // (In Debug mode, we make it a class so that we can add a finalizer - // in order to detect when the object is not freed.) - private class Sentinel - { - ~Sentinel() + // (In Debug mode, we make it a class so that we can add a finalizer + // in order to detect when the object is not freed.) + private class Sentinel { - // If this finalizer runs, someone somewhere failed to - // call Dispose, which means we've failed to leave - // a monitor! + ~Sentinel() + { + // If this finalizer runs, someone somewhere failed to + // call Dispose, which means we've failed to leave + // a monitor! #if NETSTANDARD2_0 System.Diagnostics.Debug.Fail("Undisposed lock"); #endif + } } - } - private Sentinel leakDetector; + private Sentinel leakDetector; #endif -} + } -internal class LockTimeoutException : Exception -{ - public LockTimeoutException() : base("Timeout waiting for lock") + internal class LockTimeoutException : Exception { + public LockTimeoutException() : base("Timeout waiting for lock") + { + } } } \ No newline at end of file diff --git a/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs b/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs index 468a49d4d49..c37eafde1aa 100644 --- a/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs +++ b/src/Polly/Wrap/AsyncPolicyWrap.ContextAndKeys.cs @@ -1,39 +1,40 @@ -namespace Polly.Wrap; - -public partial class AsyncPolicyWrap +namespace Polly.Wrap { - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + public partial class AsyncPolicyWrap { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); + base.SetPolicyContext(executionContext, out _, out _); + } } -} -public partial class AsyncPolicyWrap -{ - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + public partial class AsyncPolicyWrap { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); + base.SetPolicyContext(executionContext, out _, out _); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Wrap/AsyncPolicyWrap.cs b/src/Polly/Wrap/AsyncPolicyWrap.cs index 8897797bfb7..e86b432cfa6 100644 --- a/src/Polly/Wrap/AsyncPolicyWrap.cs +++ b/src/Polly/Wrap/AsyncPolicyWrap.cs @@ -3,175 +3,176 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Wrap; - -/// -/// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. -/// -public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap +namespace Polly.Wrap { - private IAsyncPolicy _outer; - private IAsyncPolicy _inner; - - /// - /// Returns the outer in this - /// - public IsPolicy Outer => _outer; - /// - /// Returns the next inner in this + /// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. /// - public IsPolicy Inner => _inner; + public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap + { + private IAsyncPolicy _outer; + private IAsyncPolicy _inner; + /// + /// Returns the outer in this + /// + public IsPolicy Outer => _outer; - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates) - { - _outer = outer; - _inner = inner; - } + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => _inner; - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outer, - _inner - ); - - /// - [DebuggerStepThrough] - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - => AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outer, - _inner - ); -} -/// -/// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. -/// -/// The return type of delegates which may be executed through the policy. -public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap -{ - private IAsyncPolicy _outerNonGeneric; - private IAsyncPolicy _innerNonGeneric; + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates) + { + _outer = outer; + _inner = inner; + } - private IAsyncPolicy _outerGeneric; - private IAsyncPolicy _innerGeneric; + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outer, + _inner + ); + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outer, + _inner + ); + } /// - /// Returns the outer in this + /// A policy that allows two (and by recursion more) async Polly policies to wrap executions of async delegates. /// - public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; + /// The return type of delegates which may be executed through the policy. + public partial class AsyncPolicyWrap : AsyncPolicy, IPolicyWrap + { + private IAsyncPolicy _outerNonGeneric; + private IAsyncPolicy _innerNonGeneric; - /// - /// Returns the next inner in this - /// - public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; + private IAsyncPolicy _outerGeneric; + private IAsyncPolicy _innerGeneric; - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates, ResultPredicates.None) - { - _outerNonGeneric = outer; - _innerGeneric = inner; - } + /// + /// Returns the outer in this + /// + public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) - { - _outerGeneric = outer; - _innerNonGeneric = inner; - } + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; - internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) - { - _outerGeneric = outer; - _innerGeneric = inner; - } + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates, ResultPredicates.None) + { + _outerNonGeneric = outer; + _innerGeneric = inner; + } - /// - protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, - bool continueOnCapturedContext) - { - if (_outerNonGeneric != null) + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) { - if (_innerNonGeneric != null) - { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerNonGeneric, - _innerNonGeneric - ); - } - else if (_innerGeneric != null) - { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerNonGeneric, - _innerGeneric - ); + _outerGeneric = outer; + _innerNonGeneric = inner; + } - } - else - { - throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); - } + internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) + { + _outerGeneric = outer; + _innerGeneric = inner; } - else if (_outerGeneric != null) + + /// + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) { - if (_innerNonGeneric != null) + if (_outerNonGeneric != null) { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerGeneric, - _innerNonGeneric - ); - + if (_innerNonGeneric != null) + { + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerNonGeneric, + _innerNonGeneric + ); + } + else if (_innerGeneric != null) + { + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerNonGeneric, + _innerGeneric + ); + + } + else + { + throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); + } } - else if (_innerGeneric != null) + else if (_outerGeneric != null) { - return AsyncPolicyWrapEngine.ImplementationAsync( - action, - context, - cancellationToken, - continueOnCapturedContext, - _outerGeneric, - _innerGeneric - ); - + if (_innerNonGeneric != null) + { + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerGeneric, + _innerNonGeneric + ); + + } + else if (_innerGeneric != null) + { + return AsyncPolicyWrapEngine.ImplementationAsync( + action, + context, + cancellationToken, + continueOnCapturedContext, + _outerGeneric, + _innerGeneric + ); + + } + else + { + throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); + } } else { - throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an inner policy."); + throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an outer policy."); } } - else - { - throw new InvalidOperationException($"A {nameof(AsyncPolicyWrap)} must define an outer policy."); - } } -} \ No newline at end of file +} diff --git a/src/Polly/Wrap/AsyncPolicyWrapEngine.cs b/src/Polly/Wrap/AsyncPolicyWrapEngine.cs index f82cc6609f3..18b33157062 100644 --- a/src/Polly/Wrap/AsyncPolicyWrapEngine.cs +++ b/src/Polly/Wrap/AsyncPolicyWrapEngine.cs @@ -2,103 +2,104 @@ using System.Threading; using System.Threading.Tasks; -namespace Polly.Wrap; - -internal static class AsyncPolicyWrapEngine +namespace Polly.Wrap { - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, + internal static class AsyncPolicyWrapEngine + { + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext); - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext); - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext); - internal static async Task ImplementationAsync( - Func> func, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - func, - ctx, - ct, + internal static async Task ImplementationAsync( + Func> func, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + func, + ctx, + ct, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext); - internal static async Task ImplementationAsync( - Func action, - Context context, - CancellationToken cancellationToken, - bool continueOnCapturedContext, - IAsyncPolicy outerPolicy, - IAsyncPolicy innerPolicy) - => await outerPolicy.ExecuteAsync( - async (ctx, ct) => await innerPolicy.ExecuteAsync( - action, - ctx, - ct, + internal static async Task ImplementationAsync( + Func action, + Context context, + CancellationToken cancellationToken, + bool continueOnCapturedContext, + IAsyncPolicy outerPolicy, + IAsyncPolicy innerPolicy) + => await outerPolicy.ExecuteAsync( + async (ctx, ct) => await innerPolicy.ExecuteAsync( + action, + ctx, + ct, + continueOnCapturedContext + ).ConfigureAwait(continueOnCapturedContext), + context, + cancellationToken, continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext), - context, - cancellationToken, - continueOnCapturedContext - ).ConfigureAwait(continueOnCapturedContext); + ).ConfigureAwait(continueOnCapturedContext); -} \ No newline at end of file + } +} diff --git a/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs b/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs index e06d6bab7c5..cf87e61fa0f 100644 --- a/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs +++ b/src/Polly/Wrap/AsyncPolicyWrapSyntax.cs @@ -2,171 +2,172 @@ using System.Linq; using Polly.Wrap; -namespace Polly; - -public partial class AsyncPolicy +namespace Polly { - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + public partial class AsyncPolicy { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); - } + return new AsyncPolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// The return type of delegates which may be executed through the policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// The return type of delegates which may be executed through the policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); + return new AsyncPolicyWrap( + this, + innerPolicy + ); + } } -} -public partial class AsyncPolicy -{ - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + public partial class AsyncPolicy { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); - } + return new AsyncPolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public AsyncPolicyWrap WrapAsync(IAsyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new AsyncPolicyWrap( - this, - innerPolicy - ); + return new AsyncPolicyWrap( + this, + innerPolicy + ); + } } -} -public partial class Policy -{ - /// - /// Creates a of the given policies. - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) + public partial class Policy { - switch (policies.Length) + /// + /// Creates a of the given policies. + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); - - default: - return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); + switch (policies.Length) + { + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); + + default: + return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); + } } - } - /// - /// Creates a of the given policies governing delegates returning values of type . - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The return type of delegates which may be executed through the policy. - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) - { - switch (policies.Length) + /// + /// Creates a of the given policies governing delegates returning values of type . + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The return type of delegates which may be executed through the policy. + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static AsyncPolicyWrap WrapAsync(params IAsyncPolicy[] policies) { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); - - default: - return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); + switch (policies.Length) + { + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new AsyncPolicyWrap((AsyncPolicy)policies[0], policies[1]); + + default: + return WrapAsync(policies[0], WrapAsync(policies.Skip(1).ToArray())); + } } } -} -/// -/// Defines extensions for configuring instances on an or . -/// -public static class IAsyncPolicyPolicyWrapExtensions -{ /// - /// Wraps the specified outer policy round the inner policy. + /// Defines extensions for configuring instances on an or . /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + public static class IAsyncPolicyPolicyWrapExtensions { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static AsyncPolicyWrap WrapAsync(this IAsyncPolicy outerPolicy, IAsyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((AsyncPolicy)outerPolicy).WrapAsync(innerPolicy); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Wrap/IPolicyWrap.cs b/src/Polly/Wrap/IPolicyWrap.cs index c475fc1a559..075f0060cdb 100644 --- a/src/Polly/Wrap/IPolicyWrap.cs +++ b/src/Polly/Wrap/IPolicyWrap.cs @@ -1,25 +1,26 @@ -namespace Polly.Wrap; - -/// -/// Defines properties and methods common to all PolicyWrap policies. -/// -public interface IPolicyWrap : IsPolicy +namespace Polly.Wrap { /// - /// Returns the outer in this + /// Defines properties and methods common to all PolicyWrap policies. /// - IsPolicy Outer { get; } + public interface IPolicyWrap : IsPolicy + { + /// + /// Returns the outer in this + /// + IsPolicy Outer { get; } + + /// + /// Returns the next inner in this + /// + IsPolicy Inner { get; } + } /// - /// Returns the next inner in this + /// Defines properties and methods common to all PolicyWrap policies generic-typed for executions returning results of type . /// - IsPolicy Inner { get; } -} + public interface IPolicyWrap : IPolicyWrap + { -/// -/// Defines properties and methods common to all PolicyWrap policies generic-typed for executions returning results of type . -/// -public interface IPolicyWrap : IPolicyWrap -{ - -} \ No newline at end of file + } +} diff --git a/src/Polly/Wrap/IPolicyWrapExtension.cs b/src/Polly/Wrap/IPolicyWrapExtension.cs index abe5c7974e6..56a6d033ca9 100644 --- a/src/Polly/Wrap/IPolicyWrapExtension.cs +++ b/src/Polly/Wrap/IPolicyWrapExtension.cs @@ -2,80 +2,81 @@ using System.Collections.Generic; using System.Linq; -namespace Polly.Wrap; - -/// -/// Extension methods for IPolicyWrap. -/// -public static class IPolicyWrapExtension +namespace Polly.Wrap { /// - /// Returns all the policies in this , in Outer-to-Inner order. + /// Extension methods for IPolicyWrap. /// - /// The for which to return policies. - /// An of all the policies in the wrap. - public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) + public static class IPolicyWrapExtension { - var childPolicies = new[] { policyWrap.Outer, policyWrap.Inner }; - foreach (var childPolicy in childPolicies) + /// + /// Returns all the policies in this , in Outer-to-Inner order. + /// + /// The for which to return policies. + /// An of all the policies in the wrap. + public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) { - if (childPolicy is IPolicyWrap anotherWrap) + var childPolicies = new[] { policyWrap.Outer, policyWrap.Inner }; + foreach (var childPolicy in childPolicies) { - foreach (var policy in anotherWrap.GetPolicies()) + if (childPolicy is IPolicyWrap anotherWrap) { - yield return policy; + foreach (var policy in anotherWrap.GetPolicies()) + { + yield return policy; + } + } + else if (childPolicy != null) + { + yield return childPolicy; } - } - else if (childPolicy != null) - { - yield return childPolicy; } } - } - /// - /// Returns all the policies in this of type , in Outer-to-Inner order. - /// - /// The for which to return policies. - /// The type of policies to return. - /// An of all the policies of the given type. - public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) - => policyWrap.GetPolicies().OfType(); + /// + /// Returns all the policies in this of type , in Outer-to-Inner order. + /// + /// The for which to return policies. + /// The type of policies to return. + /// An of all the policies of the given type. + public static IEnumerable GetPolicies(this IPolicyWrap policyWrap) + => policyWrap.GetPolicies().OfType(); - /// - /// Returns all the policies in this of type matching the filter, in Outer-to-Inner order. - /// - /// The for which to return policies. - /// A filter to apply to any policies of type found. - /// The type of policies to return. - /// An of all the policies of the given type, matching the filter. - public static IEnumerable GetPolicies(this IPolicyWrap policyWrap, Func filter) - { - if (filter == null) throw new ArgumentNullException(nameof(filter)); - return policyWrap.GetPolicies().OfType().Where(filter); - } + /// + /// Returns all the policies in this of type matching the filter, in Outer-to-Inner order. + /// + /// The for which to return policies. + /// A filter to apply to any policies of type found. + /// The type of policies to return. + /// An of all the policies of the given type, matching the filter. + public static IEnumerable GetPolicies(this IPolicyWrap policyWrap, Func filter) + { + if (filter == null) throw new ArgumentNullException(nameof(filter)); + return policyWrap.GetPolicies().OfType().Where(filter); + } - /// - /// Returns the single policy in this of type . - /// - /// The for which to search for the policy. - /// The type of policy to return. - /// A if one is found; else null. - /// InvalidOperationException, if more than one policy of the type is found in the wrap. - public static TPolicy GetPolicy(this IPolicyWrap policyWrap) - => policyWrap.GetPolicies().OfType().SingleOrDefault(); + /// + /// Returns the single policy in this of type . + /// + /// The for which to search for the policy. + /// The type of policy to return. + /// A if one is found; else null. + /// InvalidOperationException, if more than one policy of the type is found in the wrap. + public static TPolicy GetPolicy(this IPolicyWrap policyWrap) + => policyWrap.GetPolicies().OfType().SingleOrDefault(); - /// - /// Returns the single policy in this of type matching the filter. - /// - /// The for which to search for the policy. - /// A filter to apply to any policies of type found. - /// The type of policy to return. - /// A matching if one is found; else null. - /// InvalidOperationException, if more than one policy of the type is found in the wrap. - public static TPolicy GetPolicy(this IPolicyWrap policyWrap, Func filter) - { - if (filter == null) throw new ArgumentNullException(nameof(filter)); - return policyWrap.GetPolicies().OfType().SingleOrDefault(filter); + /// + /// Returns the single policy in this of type matching the filter. + /// + /// The for which to search for the policy. + /// A filter to apply to any policies of type found. + /// The type of policy to return. + /// A matching if one is found; else null. + /// InvalidOperationException, if more than one policy of the type is found in the wrap. + public static TPolicy GetPolicy(this IPolicyWrap policyWrap, Func filter) + { + if (filter == null) throw new ArgumentNullException(nameof(filter)); + return policyWrap.GetPolicies().OfType().SingleOrDefault(filter); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs b/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs index a8f2659cdbf..438c0b9e817 100644 --- a/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs +++ b/src/Polly/Wrap/PolicyWrap.ContextAndKeys.cs @@ -1,39 +1,40 @@ -namespace Polly.Wrap; - -public partial class PolicyWrap +namespace Polly.Wrap { - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + public partial class PolicyWrap { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); + base.SetPolicyContext(executionContext, out _, out _); + } } -} -public partial class PolicyWrap -{ - /// - /// Updates the execution with context from the executing . - /// - /// The execution . - /// The prior to changes by this method. - /// The prior to changes by this method. - internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + public partial class PolicyWrap { - priorPolicyWrapKey = executionContext.PolicyWrapKey; - priorPolicyKey = executionContext.PolicyKey; + /// + /// Updates the execution with context from the executing . + /// + /// The execution . + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) + { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; - if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext, out _, out _); + base.SetPolicyContext(executionContext, out _, out _); + } } -} \ No newline at end of file +} diff --git a/src/Polly/Wrap/PolicyWrap.cs b/src/Polly/Wrap/PolicyWrap.cs index 8c5843cc908..712dc775784 100644 --- a/src/Polly/Wrap/PolicyWrap.cs +++ b/src/Polly/Wrap/PolicyWrap.cs @@ -2,162 +2,163 @@ using System.Diagnostics; using System.Threading; -namespace Polly.Wrap; - -/// -/// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. -/// -public partial class PolicyWrap : Policy, IPolicyWrap +namespace Polly.Wrap { - private ISyncPolicy _outer; - private ISyncPolicy _inner; - - /// - /// Returns the outer in this - /// - public IsPolicy Outer => _outer; - /// - /// Returns the next inner in this + /// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. /// - public IsPolicy Inner => _inner; - - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates) + public partial class PolicyWrap : Policy, IPolicyWrap { - _outer = outer; - _inner = inner; - } + private ISyncPolicy _outer; + private ISyncPolicy _inner; - /// - [DebuggerStepThrough] - protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) - => PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outer, - _inner - ); - - /// - [DebuggerStepThrough] - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - => PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outer, - _inner - ); -} + /// + /// Returns the outer in this + /// + public IsPolicy Outer => _outer; -/// -/// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. -/// -/// The return type of delegates which may be executed through the policy. -public partial class PolicyWrap : Policy, IPolicyWrap -{ - private ISyncPolicy _outerNonGeneric; - private ISyncPolicy _innerNonGeneric; + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => _inner; - private ISyncPolicy _outerGeneric; - private ISyncPolicy _innerGeneric; + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates) + { + _outer = outer; + _inner = inner; + } - /// - /// Returns the outer in this - /// - public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; + /// + [DebuggerStepThrough] + protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) + => PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outer, + _inner + ); + + /// + [DebuggerStepThrough] + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + => PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outer, + _inner + ); + } /// - /// Returns the next inner in this + /// A policy that allows two (and by recursion more) Polly policies to wrap executions of delegates. /// - public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; - - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates, ResultPredicates.None) + /// The return type of delegates which may be executed through the policy. + public partial class PolicyWrap : Policy, IPolicyWrap { - _outerNonGeneric = outer; - _innerGeneric = inner; - } + private ISyncPolicy _outerNonGeneric; + private ISyncPolicy _innerNonGeneric; - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) - { - _outerGeneric = outer; - _innerNonGeneric = inner; - } + private ISyncPolicy _outerGeneric; + private ISyncPolicy _innerGeneric; - internal PolicyWrap(Policy outer, ISyncPolicy inner) - : base(outer.ExceptionPredicates, outer.ResultPredicates) - { - _outerGeneric = outer; - _innerGeneric = inner; - } + /// + /// Returns the outer in this + /// + public IsPolicy Outer => (IsPolicy)_outerGeneric ?? _outerNonGeneric; - /// - protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) - { - if (_outerNonGeneric != null) + /// + /// Returns the next inner in this + /// + public IsPolicy Inner => (IsPolicy)_innerGeneric ?? _innerNonGeneric; + + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates, ResultPredicates.None) { - if (_innerNonGeneric != null) - { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerNonGeneric, - _innerNonGeneric - ); - } - else if (_innerGeneric != null) - { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerNonGeneric, - _innerGeneric - ); + _outerNonGeneric = outer; + _innerGeneric = inner; + } - } - else - { - throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); - } + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) + { + _outerGeneric = outer; + _innerNonGeneric = inner; } - else if (_outerGeneric != null) + + internal PolicyWrap(Policy outer, ISyncPolicy inner) + : base(outer.ExceptionPredicates, outer.ResultPredicates) { - if (_innerNonGeneric != null) - { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerGeneric, - _innerNonGeneric - ); + _outerGeneric = outer; + _innerGeneric = inner; + } + /// + protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) + { + if (_outerNonGeneric != null) + { + if (_innerNonGeneric != null) + { + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerNonGeneric, + _innerNonGeneric + ); + } + else if (_innerGeneric != null) + { + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerNonGeneric, + _innerGeneric + ); + + } + else + { + throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); + } } - else if (_innerGeneric != null) + else if (_outerGeneric != null) { - return PolicyWrapEngine.Implementation( - action, - context, - cancellationToken, - _outerGeneric, - _innerGeneric - ); - + if (_innerNonGeneric != null) + { + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerGeneric, + _innerNonGeneric + ); + + } + else if (_innerGeneric != null) + { + return PolicyWrapEngine.Implementation( + action, + context, + cancellationToken, + _outerGeneric, + _innerGeneric + ); + + } + else + { + throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); + } } else { - throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an inner policy."); + throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an outer policy."); } } - else - { - throw new InvalidOperationException($"A {nameof(PolicyWrap)} must define an outer policy."); - } } -} \ No newline at end of file +} diff --git a/src/Polly/Wrap/PolicyWrapEngine.cs b/src/Polly/Wrap/PolicyWrapEngine.cs index 0ecc4316bc5..88cca6ab465 100644 --- a/src/Polly/Wrap/PolicyWrapEngine.cs +++ b/src/Polly/Wrap/PolicyWrapEngine.cs @@ -1,47 +1,48 @@ using System; using System.Threading; -namespace Polly.Wrap; - -internal static class PolicyWrapEngine +namespace Polly.Wrap { - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static class PolicyWrapEngine + { + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static TResult Implementation( - Func func, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); + internal static TResult Implementation( + Func func, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(func, ctx, ct), context, cancellationToken); - internal static void Implementation( - Action action, - Context context, - CancellationToken cancellationToken, - ISyncPolicy outerPolicy, - ISyncPolicy innerPolicy) - => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(action, ctx, ct), context, cancellationToken); -} \ No newline at end of file + internal static void Implementation( + Action action, + Context context, + CancellationToken cancellationToken, + ISyncPolicy outerPolicy, + ISyncPolicy innerPolicy) + => outerPolicy.Execute((ctx, ct) => innerPolicy.Execute(action, ctx, ct), context, cancellationToken); + } +} diff --git a/src/Polly/Wrap/PolicyWrapSyntax.cs b/src/Polly/Wrap/PolicyWrapSyntax.cs index c83b77363f5..f2ebb403ebf 100644 --- a/src/Polly/Wrap/PolicyWrapSyntax.cs +++ b/src/Polly/Wrap/PolicyWrapSyntax.cs @@ -2,171 +2,172 @@ using System.Linq; using Polly.Wrap; -namespace Polly; - -public partial class Policy +namespace Polly { - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) + public partial class Policy { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); - } + return new PolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// The return type of delegates which may be executed through the policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// The return type of delegates which may be executed through the policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); + return new PolicyWrap( + this, + innerPolicy + ); + } } -} -public partial class Policy -{ - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) + public partial class Policy { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); - } + return new PolicyWrap( + this, + innerPolicy + ); + } - /// - /// Wraps the specified inner policy. - /// - /// The inner policy. - /// PolicyWrap.PolicyWrap. - public PolicyWrap Wrap(ISyncPolicy innerPolicy) - { - if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); + /// + /// Wraps the specified inner policy. + /// + /// The inner policy. + /// PolicyWrap.PolicyWrap. + public PolicyWrap Wrap(ISyncPolicy innerPolicy) + { + if (innerPolicy == null) throw new ArgumentNullException(nameof(innerPolicy)); - return new PolicyWrap( - this, - innerPolicy - ); + return new PolicyWrap( + this, + innerPolicy + ); + } } -} -public partial class Policy -{ - /// - /// Creates a of the given policies. - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static PolicyWrap Wrap(params ISyncPolicy[] policies) + public partial class Policy { - switch (policies.Length) + /// + /// Creates a of the given policies. + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static PolicyWrap Wrap(params ISyncPolicy[] policies) { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new PolicyWrap((Policy)policies[0], policies[1]); - - default: - return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); + switch (policies.Length) + { + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new PolicyWrap((Policy)policies[0], policies[1]); + + default: + return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); + } } - } - /// - /// Creates a of the given policies governing delegates returning values of type . - /// - /// The policies to place in the wrap, outermost (at left) to innermost (at right). - /// The return type of delegates which may be executed through the policy. - /// The PolicyWrap. - /// The enumerable of policies to form the wrap must contain at least two policies. - public static PolicyWrap Wrap(params ISyncPolicy[] policies) - { - switch (policies.Length) + /// + /// Creates a of the given policies governing delegates returning values of type . + /// + /// The policies to place in the wrap, outermost (at left) to innermost (at right). + /// The return type of delegates which may be executed through the policy. + /// The PolicyWrap. + /// The enumerable of policies to form the wrap must contain at least two policies. + public static PolicyWrap Wrap(params ISyncPolicy[] policies) { - case 0: - case 1: - throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); - case 2: - return new PolicyWrap((Policy)policies[0], policies[1]); - - default: - return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); + switch (policies.Length) + { + case 0: + case 1: + throw new ArgumentException("The enumerable of policies to form the wrap must contain at least two policies.", nameof(policies)); + case 2: + return new PolicyWrap((Policy)policies[0], policies[1]); + + default: + return Wrap(policies[0], Wrap(policies.Skip(1).ToArray())); + } } } -} -/// -/// Defines extensions for configuring instances on an or . -/// -public static class ISyncPolicyPolicyWrapExtensions -{ /// - /// Wraps the specified outer policy round the inner policy. + /// Defines extensions for configuring instances on an or . /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + public static class ISyncPolicyPolicyWrapExtensions { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy) outerPolicy).Wrap(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy) outerPolicy).Wrap(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy)outerPolicy).Wrap(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy)outerPolicy).Wrap(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy)outerPolicy).Wrap(innerPolicy); - } + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy)outerPolicy).Wrap(innerPolicy); + } - /// - /// Wraps the specified outer policy round the inner policy. - /// - /// The outer policy. - /// The inner policy. - /// A instance representing the combined wrap. - public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) - { - if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); - return ((Policy)outerPolicy).Wrap(innerPolicy); + /// + /// Wraps the specified outer policy round the inner policy. + /// + /// The outer policy. + /// The inner policy. + /// A instance representing the combined wrap. + public static PolicyWrap Wrap(this ISyncPolicy outerPolicy, ISyncPolicy innerPolicy) + { + if (outerPolicy == null) throw new ArgumentNullException(nameof(outerPolicy)); + return ((Policy)outerPolicy).Wrap(innerPolicy); + } } -} \ No newline at end of file +} From a792446c4ba8d94719b67d6a2b2f9d1047372e7a Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 14:52:41 +0000 Subject: [PATCH 20/24] Revert "use var" This reverts commit 87cf9c08a49c36fafa1bdad234c882d968a92de8. --- src/Polly.Benchmarks/Cache.cs | 4 +- src/Polly.Benchmarks/Retry.cs | 4 +- .../Bulkhead/BulkheadAsyncSpecs.cs | 8 +- src/Polly.Specs/Bulkhead/BulkheadSpecs.cs | 8 +- src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs | 24 +- .../Bulkhead/BulkheadTResultAsyncSpecs.cs | 8 +- .../Bulkhead/BulkheadTResultSpecs.cs | 10 +- src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs | 8 +- src/Polly.Specs/Caching/CacheAsyncSpecs.cs | 102 ++--- src/Polly.Specs/Caching/CacheSpecs.cs | 156 ++++---- .../Caching/CacheTResultAsyncSpecs.cs | 62 +-- src/Polly.Specs/Caching/CacheTResultSpecs.cs | 102 ++--- src/Polly.Specs/Caching/ContextualTtlSpecs.cs | 20 +- .../Caching/DefaultCacheKeyStrategySpecs.cs | 4 +- .../Caching/GenericCacheProviderAsyncSpecs.cs | 10 +- .../Caching/GenericCacheProviderSpecs.cs | 14 +- src/Polly.Specs/Caching/RelativeTtlSpecs.cs | 16 +- src/Polly.Specs/Caching/ResultTtlSpecs.cs | 10 +- .../SerializingCacheProviderAsyncSpecs.cs | 192 +++++----- .../Caching/SerializingCacheProviderSpecs.cs | 192 +++++----- src/Polly.Specs/Caching/SlidingTtlSpecs.cs | 6 +- .../AdvancedCircuitBreakerAsyncSpecs.cs | 170 ++++----- .../AdvancedCircuitBreakerSpecs.cs | 316 ++++++++-------- .../CircuitBreakerAsyncSpecs.cs | 174 ++++----- .../CircuitBreaker/CircuitBreakerSpecs.cs | 284 +++++++------- .../CircuitBreakerTResultAsyncSpecs.cs | 150 ++++---- ...BreakerTResultMixedResultExceptionSpecs.cs | 48 +-- .../CircuitBreakerTResultSpecs.cs | 248 ++++++------ src/Polly.Specs/ContextSpecs.cs | 14 +- src/Polly.Specs/Custom/CustomAsyncSpecs.cs | 22 +- src/Polly.Specs/Custom/CustomSpecs.cs | 22 +- .../Custom/CustomTResultAsyncSpecs.cs | 28 +- src/Polly.Specs/Custom/CustomTResultSpecs.cs | 28 +- .../Fallback/FallbackAsyncSpecs.cs | 110 +++--- src/Polly.Specs/Fallback/FallbackSpecs.cs | 358 +++++++++--------- .../Fallback/FallbackTResultAsyncSpecs.cs | 110 +++--- .../Fallback/FallbackTResultSpecs.cs | 204 +++++----- .../Helpers/ContextualPolicyExtensions.cs | 2 +- .../ContextualPolicyExtensionsAsync.cs | 2 +- .../AddBehaviourIfHandleEngine.cs | 4 +- .../AsyncAddBehaviourIfHandleEngine.cs | 4 +- src/Polly.Specs/Helpers/PolicyExtensions.cs | 12 +- .../Helpers/PolicyExtensionsAsync.cs | 12 +- .../Helpers/PolicyTResultExtensions.cs | 6 +- .../Helpers/PolicyTResultExtensionsAsync.cs | 6 +- .../RateLimit/IRateLimiterExtensions.cs | 6 +- .../IAsyncPolicyExtensionsSpecs.cs | 2 +- src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs | 4 +- src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs | 6 +- src/Polly.Specs/NoOp/NoOpSpecs.cs | 10 +- src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs | 2 +- src/Polly.Specs/NoOp/NoOpTResultSpecs.cs | 6 +- src/Polly.Specs/PolicyAsyncSpecs.cs | 16 +- .../PolicyContextAndKeyAsyncSpecs.cs | 24 +- src/Polly.Specs/PolicyContextAndKeySpecs.cs | 24 +- src/Polly.Specs/PolicySpecs.cs | 12 +- src/Polly.Specs/PolicyTResultAsyncSpecs.cs | 12 +- src/Polly.Specs/PolicyTResultSpecs.cs | 12 +- .../RateLimit/RateLimitPolicySpecsBase.cs | 34 +- .../RateLimitPolicyTResultSpecsBase.cs | 4 +- .../RateLimit/RateLimitSpecsBase.cs | 8 +- .../TokenBucketRateLimiterTestsBase.cs | 32 +- .../Registry/ConcurrentPolicyRegistrySpecs.cs | 52 +-- .../Registry/PolicyRegistrySpecs.cs | 74 ++-- .../Registry/ReadOnlyPolicyRegistrySpecs.cs | 44 +-- src/Polly.Specs/Retry/RetryAsyncSpecs.cs | 112 +++--- .../Retry/RetryForeverAsyncSpecs.cs | 86 ++--- src/Polly.Specs/Retry/RetrySpecs.cs | 108 +++--- .../RetryTResultMixedResultExceptionSpecs.cs | 20 +- src/Polly.Specs/Retry/RetryTResultSpecs.cs | 174 ++++----- .../Retry/RetryTResultSpecsAsync.cs | 116 +++--- .../Retry/WaitAndRetryAsyncSpecs.cs | 134 +++---- .../Retry/WaitAndRetryForeverAsyncSpecs.cs | 88 ++--- .../Retry/WaitAndRetryForeverSpecs.cs | 6 +- .../WaitAndRetryForeverTResultAsyncSpecs.cs | 2 +- .../Retry/WaitAndRetryForeverTResultSpecs.cs | 2 +- src/Polly.Specs/Retry/WaitAndRetrySpecs.cs | 116 +++--- .../Retry/WaitAndRetryTResultAsyncSpecs.cs | 2 +- .../Retry/WaitAndRetryTResultSpecs.cs | 2 +- src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs | 74 ++-- src/Polly.Specs/Timeout/TimeoutSpecs.cs | 80 ++-- src/Polly.Specs/Timeout/TimeoutSpecsBase.cs | 6 +- .../Timeout/TimeoutTResultAsyncSpecs.cs | 70 ++-- .../Timeout/TimeoutTResultSpecs.cs | 86 ++--- .../Wrap/IPolicyWrapExtensionSpecs.cs | 62 +-- .../Wrap/PolicyWrapContextAndKeySpecs.cs | 58 +-- .../Wrap/PolicyWrapContextAndKeySpecsAsync.cs | 56 +-- src/Polly.Specs/Wrap/PolicyWrapSpecs.cs | 92 ++--- src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs | 44 +-- src/Polly/AsyncPolicy.ExecuteOverloads.cs | 4 +- .../AsyncPolicy.TResult.ExecuteOverloads.cs | 4 +- src/Polly/Caching/AsyncCacheEngine.cs | 6 +- .../Caching/AsyncGenericCacheProvider.cs | 2 +- .../Caching/AsyncSerializingCacheProvider.cs | 4 +- src/Polly/Caching/CacheEngine.cs | 6 +- src/Polly/Caching/ContextualTtl.cs | 4 +- src/Polly/Caching/GenericCacheProvider.cs | 2 +- src/Polly/Caching/NonSlidingTtl.cs | 4 +- src/Polly/Caching/SerializingCacheProvider.cs | 4 +- .../AdvancedCircuitBreakerSyntax.cs | 4 +- .../AdvancedCircuitBreakerTResultSyntax.cs | 4 +- .../AdvancedCircuitController.cs | 2 +- .../AsyncAdvancedCircuitBreakerSyntax.cs | 4 +- ...syncAdvancedCircuitBreakerTResultSyntax.cs | 4 +- .../AsyncCircuitBreakerEngine.cs | 4 +- .../AsyncCircuitBreakerSyntax.cs | 4 +- .../AsyncCircuitBreakerTResultSyntax.cs | 4 +- .../CircuitBreaker/CircuitBreakerEngine.cs | 4 +- .../CircuitBreaker/CircuitBreakerSyntax.cs | 4 +- .../CircuitBreakerTResultSyntax.cs | 4 +- .../CircuitBreaker/CircuitStateController.cs | 6 +- .../CircuitBreaker/RollingHealthMetrics.cs | 6 +- .../CircuitBreaker/SingleHealthMetrics.cs | 2 +- src/Polly/Fallback/AsyncFallbackEngine.cs | 4 +- src/Polly/Fallback/FallbackEngine.cs | 4 +- src/Polly/Policy.ExecuteOverloads.cs | 4 +- src/Polly/Policy.TResult.ExecuteOverloads.cs | 4 +- src/Polly/PolicyBase.cs | 2 +- src/Polly/RateLimit/AsyncRateLimitEngine.cs | 2 +- src/Polly/RateLimit/AsyncRateLimitSyntax.cs | 2 +- .../RateLimit/AsyncRateLimitTResultSyntax.cs | 2 +- .../LockFreeTokenBucketRateLimiter.cs | 14 +- src/Polly/RateLimit/RateLimitEngine.cs | 2 +- src/Polly/RateLimit/RateLimitSyntax.cs | 2 +- src/Polly/RateLimit/RateLimitTResultSyntax.cs | 2 +- src/Polly/Registry/PolicyRegistry.cs | 4 +- src/Polly/Retry/AsyncRetryEngine.cs | 10 +- src/Polly/Retry/AsyncRetrySyntax.cs | 2 +- src/Polly/Retry/AsyncRetryTResultSyntax.cs | 2 +- src/Polly/Retry/RetryEngine.cs | 10 +- src/Polly/Timeout/AsyncTimeoutEngine.cs | 10 +- src/Polly/Timeout/TimeoutEngine.cs | 8 +- src/Polly/Utilities/TimedLock.cs | 2 +- 133 files changed, 2731 insertions(+), 2731 deletions(-) diff --git a/src/Polly.Benchmarks/Cache.cs b/src/Polly.Benchmarks/Cache.cs index 3ef233a84af..558fbdd0d2a 100644 --- a/src/Polly.Benchmarks/Cache.cs +++ b/src/Polly.Benchmarks/Cache.cs @@ -68,13 +68,13 @@ public MemoryCacheProvider(IMemoryCache memoryCache) public (bool, object) TryGet(string key) { - var cacheHit = _cache.TryGetValue(key, out var value); + bool cacheHit = _cache.TryGetValue(key, out var value); return (cacheHit, value); } public void Put(string key, object value, Ttl ttl) { - var remaining = DateTimeOffset.MaxValue - DateTimeOffset.UtcNow; + TimeSpan remaining = DateTimeOffset.MaxValue - DateTimeOffset.UtcNow; var options = new MemoryCacheEntryOptions(); if (ttl.SlidingExpiration) diff --git a/src/Polly.Benchmarks/Retry.cs b/src/Polly.Benchmarks/Retry.cs index f9ad1a5390b..0d5ae5ed606 100644 --- a/src/Polly.Benchmarks/Retry.cs +++ b/src/Polly.Benchmarks/Retry.cs @@ -50,7 +50,7 @@ public async Task Retry_Asynchronous_With_Result_Succeeds_With_Cancellation [Benchmark] public void Retry_Synchronous_Throws_Then_Succeeds() { - var count = 0; + int count = 0; SyncPolicy.Execute(() => { @@ -64,7 +64,7 @@ public void Retry_Synchronous_Throws_Then_Succeeds() [Benchmark] public async Task Retry_Asynchronous_Throws_Then_Succeeds() { - var count = 0; + int count = 0; await AsyncPolicy.ExecuteAsync(() => { diff --git a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs index bc36194e92a..da37e103487 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadAsyncSpecs.cs @@ -54,16 +54,16 @@ public void Should_throw_when_onBulkheadRejected_is_null() [Fact] public void Should_call_onBulkheadRejected_with_passed_context() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnRejected = null; Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) { - var tcs = new TaskCompletionSource(); - using (var cancellationSource = new CancellationTokenSource()) + TaskCompletionSource tcs = new TaskCompletionSource(); + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) { Task.Run(() => { bulkhead.ExecuteAsync(async () => { await tcs.Task; }); }); diff --git a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs index 8cd0804bf39..7e718d7ab47 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadSpecs.cs @@ -52,15 +52,15 @@ public void Should_throw_when_onBulkheadRejected_is_null() [Fact] public void Should_call_onBulkheadRejected_with_passed_context() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnRejected = null; Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; - using (var bulkhead = Policy.Bulkhead(1, onRejected)) + using (BulkheadPolicy bulkhead = Policy.Bulkhead(1, onRejected)) { - var tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); Task.Run(() => { bulkhead.Execute(() => { tcs.Task.Wait(); }); }); diff --git a/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs b/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs index 5ac236ebbc3..4d6a6fa9420 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadSpecsBase.cs @@ -93,18 +93,18 @@ public void Should_control_executions_per_specification(int maxParallelization, TotalActions = totalActions; Scenario = $"MaxParallelization {maxParallelization}; MaxQueuing {maxQueuingActions}; TotalActions {totalActions}; CancelQueuing {cancelQueuing}; CancelExecuting {cancelExecuting}: {scenario}"; - var bulkhead = GetBulkhead(maxParallelization, maxQueuingActions); + IBulkheadPolicy bulkhead = GetBulkhead(maxParallelization, maxQueuingActions); using (bulkhead) { BulkheadForStats = bulkhead; // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). Actions = new TraceableAction[totalActions]; - for (var i = 0; i < totalActions; i++) { Actions[i] = new TraceableAction(i, StatusChangedEvent, TestOutputHelper); } + for (int i = 0; i < totalActions; i++) { Actions[i] = new TraceableAction(i, StatusChangedEvent, TestOutputHelper); } // Throw all the delegates at the bulkhead simultaneously. Tasks = new Task[totalActions]; - for (var i = 0; i < totalActions; i++) { Tasks[i] = ExecuteOnBulkhead(bulkhead, Actions[i]); } + for (int i = 0; i < totalActions; i++) { Tasks[i] = ExecuteOnBulkhead(bulkhead, Actions[i]); } OutputStatus("Immediately after queueing..."); @@ -204,7 +204,7 @@ protected void UpdateActuals() { ActualCompleted = ActualCancelled = ActualExecuting = ActualRejects = ActualQueuing = ActualFaulted = 0; - foreach (var action in Actions) + foreach (TraceableAction action in Actions) { switch (action.Status) { @@ -285,7 +285,7 @@ protected AssertionFailure ActualsMatchExpecteds() protected AssertionFailure AllTasksCompleted() { - var countTasksCompleted = Tasks.Count(t => t.IsCompleted); + int countTasksCompleted = Tasks.Count(t => t.IsCompleted); if (countTasksCompleted < TotalActions) { return new AssertionFailure(TotalActions, countTasksCompleted, nameof(countTasksCompleted)); @@ -296,7 +296,7 @@ protected AssertionFailure AllTasksCompleted() protected void EnsureNoUnbservedTaskExceptions() { - for (var i = 0; i < Tasks.Length; i++) + for (int i = 0; i < Tasks.Length; i++) { try { @@ -313,14 +313,14 @@ protected void EnsureNoUnbservedTaskExceptions() protected AssertionFailure Expect(int expected, Func actualFunc, string measure) { - var actual = actualFunc(); + int actual = actualFunc(); return actual != expected ? new AssertionFailure(expected, actual, measure) : null; } protected void Within(TimeSpan timeSpan, Func actionContainingAssertions) { - var permitted = timeSpan; - var watch = Stopwatch.StartNew(); + TimeSpan permitted = timeSpan; + Stopwatch watch = Stopwatch.StartNew(); while (true) { var potentialFailure = actionContainingAssertions(); @@ -336,7 +336,7 @@ protected void Within(TimeSpan timeSpan, Func actionContaining throw new InvalidOperationException("Code should never reach here. Preceding assertion should fail."); } - var signaled = StatusChangedEvent.WaitOne(ShimTimeSpan); + bool signaled = StatusChangedEvent.WaitOne(ShimTimeSpan); if (signaled) { // Following TraceableAction.CaptureCompletion() signalling the AutoResetEvent, @@ -360,7 +360,7 @@ protected void OutputStatus(string statusHeading) TestOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", ActualBulkheadFree, MaxParallelization); TestOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", ActualQueueFree, MaxQueuingActions); - for (var i = 0; i < Actions.Length; i++) + for (int i = 0; i < Actions.Length; i++) { TestOutputHelper.WriteLine("Action {0}: {1}", i, Actions[i].Status); } @@ -382,7 +382,7 @@ public void Dispose() if (Actions != null) { - foreach (var action in Actions) + foreach (TraceableAction action in Actions) { action.Dispose(); } diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs index 0a03318dce9..3bf4862a945 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultAsyncSpecs.cs @@ -55,16 +55,16 @@ public void Should_throw_when_onBulkheadRejected_is_null() [Fact] public void Should_call_onBulkheadRejected_with_passed_context() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnRejected = null; Func onRejectedAsync = async ctx => { contextPassedToOnRejected = ctx; await TaskHelper.EmptyTask; }; using (var bulkhead = Policy.BulkheadAsync(1, onRejectedAsync)) { - var tcs = new TaskCompletionSource(); - using (var cancellationSource = new CancellationTokenSource()) + TaskCompletionSource tcs = new TaskCompletionSource(); + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) { Task.Run(() => { bulkhead.ExecuteAsync(async () => diff --git a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs index 99630888728..3dded928396 100644 --- a/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs +++ b/src/Polly.Specs/Bulkhead/BulkheadTResultSpecs.cs @@ -55,16 +55,16 @@ public void Should_throw_when_onBulkheadRejected_is_null() [Fact] public void Should_call_onBulkheadRejected_with_passed_context() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnRejected = null; Action onRejected = ctx => { contextPassedToOnRejected = ctx; }; - using (var bulkhead = Policy.Bulkhead(1, onRejected)) + using (BulkheadPolicy bulkhead = Policy.Bulkhead(1, onRejected)) { - var tcs = new TaskCompletionSource(); - using (var cancellationSource = new CancellationTokenSource()) + TaskCompletionSource tcs = new TaskCompletionSource(); + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) { Task.Run(() => { bulkhead.Execute(() => diff --git a/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs b/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs index 0dbcd73ac2d..640a413d63e 100644 --- a/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs +++ b/src/Polly.Specs/Caching/AbsoluteTtlSpecs.cs @@ -37,7 +37,7 @@ public void Should_be_able_to_configure_for_past() [Fact] public void Should_return_zero_ttl_if_configured_to_expire_in_past() { - var ttlStrategy = new AbsoluteTtl(SystemClock.DateTimeOffsetUtcNow().Subtract(TimeSpan.FromTicks(1))); + AbsoluteTtl ttlStrategy = new AbsoluteTtl(SystemClock.DateTimeOffsetUtcNow().Subtract(TimeSpan.FromTicks(1))); ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.Zero); } @@ -45,10 +45,10 @@ public void Should_return_zero_ttl_if_configured_to_expire_in_past() [Fact] public void Should_return_timespan_reflecting_time_until_expiry() { - var today = DateTime.Today; - var tomorrow = today.AddDays(1); + DateTime today = DateTime.Today; + DateTime tomorrow = today.AddDays(1); - var ttlStrategy = new AbsoluteTtl(tomorrow); + AbsoluteTtl ttlStrategy = new AbsoluteTtl(tomorrow); SystemClock.DateTimeOffsetUtcNow = () => today; ttlStrategy.GetTtl(new Context("someOperationKey"), null).Timespan.Should().Be(TimeSpan.FromDays(1)); diff --git a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs index 43651f19b69..fd8884db1e1 100644 --- a/src/Polly.Specs/Caching/CacheAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheAsyncSpecs.cs @@ -56,7 +56,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await cache.ExecuteAsync(async _ => { @@ -78,13 +78,13 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -96,14 +96,14 @@ public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_exp const string operationKey = "SomeOperationKey"; IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); + TimeSpan ttl = TimeSpan.FromMinutes(30); var cache = Policy.CacheAsync(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func> func = async _ => { delegateInvocations++; @@ -111,13 +111,13 @@ public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_exp return valueToReturn; }; - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); SystemClock.DateTimeOffsetUtcNow = () => fixedTime; // First execution should execute delegate and put result in the cache. (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); @@ -144,13 +144,13 @@ public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_do IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeFalse(); fromCache2.Should().BeNull(); } @@ -163,7 +163,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_pri var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; + int delegateInvocations = 0; Func> func = async _ => { delegateInvocations++; @@ -187,12 +187,12 @@ public async Task Should_allow_custom_FuncCacheKeyStrategy() IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - var person1 = new object(); + object person1 = new object(); await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var person2 = new object(); + object person2 = new object(); await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var funcExecuted = false; + bool funcExecuted = false; Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); @@ -212,12 +212,12 @@ public async Task Should_allow_custom_ICacheKeyStrategy() ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - var person1 = new object(); + object person1 = new object(); await stubCacheProvider.PutAsync("person1", person1, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var person2 = new object(); + object person2 = new object(); await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var funcExecuted = false; + bool funcExecuted = false; Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new object(); }; (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); @@ -240,13 +240,13 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -255,14 +255,14 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() { ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); const string operationKey = "SomeOperationKey"; IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await cache.ExecuteAsync(async _ => { @@ -284,13 +284,13 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -299,7 +299,7 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() { ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); const string operationKey = "SomeOperationKey"; @@ -307,7 +307,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await cache.ExecuteAsync(async _ => { @@ -338,7 +338,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await wrap.ExecuteAsync(async _ => { @@ -365,7 +365,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await wrap.ExecuteAsync(async _ => { @@ -392,7 +392,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await wrap.ExecuteAsync(async _ => { @@ -412,12 +412,12 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac [Fact] public async Task Should_always_execute_delegate_if_execution_key_not_set() { - var valueToReturn = Guid.NewGuid().ToString(); + string valueToReturn = Guid.NewGuid().ToString(); var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - var func = async () => { + int delegateInvocations = 0; + Func> func = async () => { delegateInvocations++; await TaskHelper.EmptyTask; return valueToReturn; @@ -433,11 +433,11 @@ public async Task Should_always_execute_delegate_if_execution_key_not_set() [Fact] public void Should_always_execute_delegate_if_execution_is_void_returning() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; + int delegateInvocations = 0; Func action = async _ => { delegateInvocations++; await TaskHelper.EmptyTask; }; cache.ExecuteAsync(action, new Context(operationKey)); @@ -459,9 +459,9 @@ public async Task Should_honour_cancellation_even_if_prior_execution_has_cached( var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func> func = async (_, _) => { // delegate does not observe cancellation token; test is whether CacheEngine does. @@ -489,7 +489,7 @@ public async Task Should_honour_cancellation_during_delegate_execution_and_not_p IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); Func> func = async (_, ct) => { @@ -502,7 +502,7 @@ public async Task Should_honour_cancellation_during_delegate_execution_and_not_p cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) .Should().Throw(); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); } @@ -514,7 +514,7 @@ public async Task Should_honour_cancellation_during_delegate_execution_and_not_p [Fact] public async Task Should_call_onError_delegate_if_cache_get_errors() { - var ex = new Exception(); + Exception ex = new Exception(); IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); Exception exceptionFromCacheProvider = null; @@ -529,7 +529,7 @@ public async Task Should_call_onError_delegate_if_cache_get_errors() await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; // Even though value is in cache, get will error; so value is returned from execution. @@ -550,7 +550,7 @@ public async Task Should_call_onError_delegate_if_cache_get_errors() [Fact] public async Task Should_call_onError_delegate_if_cache_put_errors() { - var ex = new Exception(); + Exception ex = new Exception(); IAsyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); Exception exceptionFromCacheProvider = null; @@ -562,7 +562,7 @@ public async Task Should_call_onError_delegate_if_cache_put_errors() var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); @@ -572,7 +572,7 @@ public async Task Should_call_onError_delegate_if_cache_put_errors() exceptionFromCacheProvider.Should().Be(ex); // failed to put it in the cache - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeFalse(); fromCache2.Should().BeNull(); } @@ -586,7 +586,7 @@ public async Task Should_execute_oncacheget_after_got_from_cache() const string operationKey = "SomeOperationKey"; string keyPassedToDelegate = null; - var contextToExecute = new Context(operationKey); + Context contextToExecute = new Context(operationKey); Context contextPassedToDelegate = null; Action noErrorHandling = (_, _, _) => { }; @@ -597,7 +597,7 @@ public async Task Should_execute_oncacheget_after_got_from_cache() var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await cache.ExecuteAsync(async _ => { delegateExecuted = true; @@ -620,7 +620,7 @@ public async Task Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_ho string keyPassedToOnCacheMiss = null; string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); + Context contextToExecute = new Context(operationKey); Context contextPassedToOnCacheMiss = null; Context contextPassedToOnCachePut = null; @@ -632,13 +632,13 @@ public async Task Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_ho IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, contextToExecute)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); @@ -655,7 +655,7 @@ public async Task Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_no string keyPassedToOnCacheMiss = null; string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); + Context contextToExecute = new Context(operationKey); Context contextPassedToOnCacheMiss = null; Context contextPassedToOnCachePut = null; @@ -667,7 +667,7 @@ public async Task Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_no IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); @@ -680,12 +680,12 @@ public async Task Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_no [Fact] public async Task Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() { - var valueToReturn = Guid.NewGuid().ToString(); + string valueToReturn = Guid.NewGuid().ToString(); Action noErrorHandling = (_, _, _) => { }; Action emptyDelegate = (_, _) => { }; - var onCacheMissExecuted = false; + bool onCacheMissExecuted = false; Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; var cache = Policy.CacheAsync(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); diff --git a/src/Polly.Specs/Caching/CacheSpecs.cs b/src/Polly.Specs/Caching/CacheSpecs.cs index f58fc012e86..3db2202c5e3 100644 --- a/src/Polly.Specs/Caching/CacheSpecs.cs +++ b/src/Polly.Specs/Caching/CacheSpecs.cs @@ -53,10 +53,10 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; cache.Execute(_ => { @@ -75,15 +75,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -95,28 +95,28 @@ public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_e const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); - var cache = Policy.Cache(stubCacheProvider, ttl); + TimeSpan ttl = TimeSpan.FromMinutes(30); + CachePolicy cache = Policy.Cache(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func func = _ => { delegateInvocations++; return valueToReturn; }; - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); SystemClock.DateTimeOffsetUtcNow = () => fixedTime; // First execution should execute delegate and put result in the cache. cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); @@ -141,15 +141,15 @@ public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeFalse(); fromCache2.Should().BeNull(); } @@ -160,9 +160,9 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_exe const string valueToReturn = "valueToReturn"; const string operationKey = "SomeOperationKey"; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; + int delegateInvocations = 0; Func func = _ => { delegateInvocations++; @@ -183,14 +183,14 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_exe public void Should_allow_custom_FuncCacheKeyStrategy() { ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); - var person1 = new object(); + object person1 = new object(); stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - var person2 = new object(); + object person2 = new object(); stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; + bool funcExecuted = false; Func func = _ => { funcExecuted = true; return new object(); }; cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); @@ -208,14 +208,14 @@ public void Should_allow_custom_ICacheKeyStrategy() ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); - var person1 = new object(); + object person1 = new object(); stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); - var person2 = new object(); + object person2 = new object(); stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; + bool funcExecuted = false; Func func = _ => { funcExecuted = true; return new object(); }; cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); @@ -236,15 +236,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -253,14 +253,14 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() { ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; cache.Execute(_ => { @@ -279,15 +279,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -296,15 +296,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() { ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; cache.Execute(_ => { @@ -328,13 +328,13 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(cache, noop); + PolicyWrap wrap = Policy.Wrap(cache, noop); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; wrap.Execute(_ => { @@ -354,13 +354,13 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(noop, cache); + PolicyWrap wrap = Policy.Wrap(noop, cache); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; wrap.Execute(_ => { @@ -380,13 +380,13 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(noop, cache, noop); + PolicyWrap wrap = Policy.Wrap(noop, cache, noop); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; wrap.Execute(_ => { @@ -405,12 +405,12 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol [Fact] public void Should_always_execute_delegate_if_execution_key_not_set() { - var valueToReturn = Guid.NewGuid().ToString(); + string valueToReturn = Guid.NewGuid().ToString(); - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - var func = () => + int delegateInvocations = 0; + Func func = () => { delegateInvocations++; return valueToReturn; @@ -426,11 +426,11 @@ public void Should_always_execute_delegate_if_execution_key_not_set() [Fact] public void Should_always_execute_delegate_if_execution_is_void_returning() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; + int delegateInvocations = 0; Action action = _ => { delegateInvocations++; }; cache.Execute(action, new Context(operationKey)); @@ -450,11 +450,11 @@ public void Should_honour_cancellation_even_if_prior_execution_has_cached() const string valueToReturn = "valueToReturn"; const string operationKey = "SomeOperationKey"; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func func = (_, _) => { // delegate does not observe cancellation token; test is whether CacheEngine does. @@ -479,9 +479,9 @@ public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_ const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); Func func = (_, ct) => { @@ -493,7 +493,7 @@ public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_ cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) .Should().Throw(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); } @@ -505,7 +505,7 @@ public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_ [Fact] public void Should_call_onError_delegate_if_cache_get_errors() { - var ex = new Exception(); + Exception ex = new Exception(); ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: ex, putException: null); Exception exceptionFromCacheProvider = null; @@ -516,11 +516,11 @@ public void Should_call_onError_delegate_if_cache_get_errors() Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; // Even though value is in cache, get will error; so value is returned from execution. @@ -539,7 +539,7 @@ public void Should_call_onError_delegate_if_cache_get_errors() [Fact] public void Should_call_onError_delegate_if_cache_put_errors() { - var ex = new Exception(); + Exception ex = new Exception(); ISyncCacheProvider stubCacheProvider = new StubErroringCacheProvider(getException: null, putException: ex); Exception exceptionFromCacheProvider = null; @@ -549,9 +549,9 @@ public void Should_call_onError_delegate_if_cache_put_errors() Action onError = (_, _, exc) => { exceptionFromCacheProvider = exc; }; - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); @@ -561,7 +561,7 @@ public void Should_call_onError_delegate_if_cache_put_errors() exceptionFromCacheProvider.Should().Be(ex); // failed to put it in the cache - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeFalse(); fromCache2.Should().BeNull(); @@ -576,7 +576,7 @@ public void Should_execute_oncacheget_after_got_from_cache() const string operationKey = "SomeOperationKey"; string keyPassedToDelegate = null; - var contextToExecute = new Context(operationKey); + Context contextToExecute = new Context(operationKey); Context contextPassedToDelegate = null; Action noErrorHandling = (_, _, _) => { }; @@ -584,10 +584,10 @@ public void Should_execute_oncacheget_after_got_from_cache() Action onCacheAction = (ctx, key) => { contextPassedToDelegate = ctx; keyPassedToDelegate = key; }; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, onCacheAction, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; cache.Execute(_ => { delegateExecuted = true; @@ -609,7 +609,7 @@ public void Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_val string keyPassedToOnCacheMiss = null; string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); + Context contextToExecute = new Context(operationKey); Context contextPassedToOnCacheMiss = null; Context contextPassedToOnCachePut = null; @@ -619,15 +619,15 @@ public void Should_execute_oncachemiss_and_oncacheput_if_cache_does_not_hold_val Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, contextToExecute).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); @@ -647,7 +647,7 @@ public void Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold string keyPassedToOnCacheMiss = null; string keyPassedToOnCachePut = null; - var contextToExecute = new Context(operationKey); + Context contextToExecute = new Context(operationKey); Context contextPassedToOnCacheMiss = null; Context contextPassedToOnCachePut = null; @@ -657,9 +657,9 @@ public void Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold Action onCachePut = (ctx, key) => { contextPassedToOnCachePut = ctx; keyPassedToOnCachePut = key; }; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); + CachePolicy cache = Policy.Cache(stubCacheProvider, new RelativeTtl(TimeSpan.Zero), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, onCachePut, noErrorHandling, noErrorHandling); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); @@ -675,15 +675,15 @@ public void Should_execute_oncachemiss_but_not_oncacheput_if_cache_does_not_hold [Fact] public void Should_not_execute_oncachemiss_if_dont_query_cache_because_cache_key_not_set() { - var valueToReturn = Guid.NewGuid().ToString(); + string valueToReturn = Guid.NewGuid().ToString(); Action noErrorHandling = (_, _, _) => { }; Action emptyDelegate = (_, _) => { }; - var onCacheMissExecuted = false; + bool onCacheMissExecuted = false; Action onCacheMiss = (_, _) => { onCacheMissExecuted = true; }; - var cache = Policy.Cache(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), new RelativeTtl(TimeSpan.MaxValue), DefaultCacheKeyStrategy.Instance, emptyDelegate, onCacheMiss, emptyDelegate, noErrorHandling, noErrorHandling); cache.Execute(() => valueToReturn /*, no operation key */).Should().Be(valueToReturn); diff --git a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs index 705ff4414a4..e58fb6fe45b 100644 --- a/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/CacheTResultAsyncSpecs.cs @@ -55,7 +55,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await cache.ExecuteAsync(async _ => { @@ -77,13 +77,13 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -95,14 +95,14 @@ public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_exp const string operationKey = "SomeOperationKey"; IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); + TimeSpan ttl = TimeSpan.FromMinutes(30); var cache = Policy.CacheAsync(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func> func = async _ => { delegateInvocations++; @@ -110,14 +110,14 @@ public async Task Should_execute_delegate_and_put_value_in_cache_but_when_it_exp return valueToReturn; }; - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); SystemClock.DateTimeOffsetUtcNow = () => fixedTime; // First execution should execute delegate and put result in the cache. (await cache.ExecuteAsync(func, new Context(operationKey))).Should().Be(valueToReturn); delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); @@ -144,13 +144,13 @@ public async Task Should_execute_delegate_but_not_put_value_in_cache_if_cache_do IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.Zero); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeFalse(); fromCache2.Should().BeNull(); } @@ -163,7 +163,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_pri var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; + int delegateInvocations = 0; Func> func = async _ => { delegateInvocations++; @@ -192,7 +192,7 @@ public async Task Should_allow_custom_FuncCacheKeyStrategy() object person2 = new ResultClass(ResultPrimitive.Good, "person2"); await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var funcExecuted = false; + bool funcExecuted = false; Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); @@ -218,7 +218,7 @@ public async Task Should_allow_custom_ICacheKeyStrategy() object person2 = new ResultClass(ResultPrimitive.Good, "person2"); await stubCacheProvider.PutAsync("person2", person2, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var funcExecuted = false; + bool funcExecuted = false; Func> func = async _ => { funcExecuted = true; await TaskHelper.EmptyTask; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; (await cache.ExecuteAsync(func, new Context("person", new { id = "1" }.AsDictionary()))).Should().BeSameAs(person1); @@ -241,13 +241,13 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -256,14 +256,14 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() { ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); const string operationKey = "SomeOperationKey"; IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await cache.ExecuteAsync(async _ => { @@ -285,13 +285,13 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); (await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return valueToReturn; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -300,7 +300,7 @@ public async Task Should_execute_delegate_and_put_value_in_cache_if_cache_does_n public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() { ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); const string operationKey = "SomeOperationKey"; @@ -308,7 +308,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await cache.ExecuteAsync(async _ => { @@ -339,7 +339,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await wrap.ExecuteAsync(async _ => { @@ -366,7 +366,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await wrap.ExecuteAsync(async _ => { @@ -393,7 +393,7 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac await stubCacheProvider.PutAsync(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue), CancellationToken.None, false); - var delegateExecuted = false; + bool delegateExecuted = false; (await wrap.ExecuteAsync(async _ => { @@ -413,12 +413,12 @@ public async Task Should_return_value_from_cache_and_not_execute_delegate_if_cac [Fact] public async Task Should_always_execute_delegate_if_execution_key_not_set() { - var valueToReturn = Guid.NewGuid().ToString(); + string valueToReturn = Guid.NewGuid().ToString(); var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - var func = async () => { + int delegateInvocations = 0; + Func> func = async () => { delegateInvocations++; await TaskHelper.EmptyTask; return valueToReturn; @@ -443,9 +443,9 @@ public async Task Should_honour_cancellation_even_if_prior_execution_has_cached( var cache = Policy.CacheAsync(new StubCacheProvider(), TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func> func = async (_, _) => { // delegate does not observe cancellation token; test is whether CacheEngine does. @@ -473,7 +473,7 @@ public async Task Should_honour_cancellation_during_delegate_execution_and_not_p IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); Func> func = async (_, ct) => { @@ -486,7 +486,7 @@ public async Task Should_honour_cancellation_during_delegate_execution_and_not_p cache.Awaiting(policy => policy.ExecuteAsync(func, new Context(operationKey), tokenSource.Token)) .Should().Throw(); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); } diff --git a/src/Polly.Specs/Caching/CacheTResultSpecs.cs b/src/Polly.Specs/Caching/CacheTResultSpecs.cs index 34076be486f..cd0a38c4ea1 100644 --- a/src/Polly.Specs/Caching/CacheTResultSpecs.cs +++ b/src/Polly.Specs/Caching/CacheTResultSpecs.cs @@ -52,10 +52,10 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; cache.Execute(_ => { @@ -74,15 +74,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -94,28 +94,28 @@ public void Should_execute_delegate_and_put_value_in_cache_but_when_it_expires_e const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var ttl = TimeSpan.FromMinutes(30); - var cache = Policy.Cache(stubCacheProvider, ttl); + TimeSpan ttl = TimeSpan.FromMinutes(30); + CachePolicy cache = Policy.Cache(stubCacheProvider, ttl); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func func = _ => { delegateInvocations++; return valueToReturn; }; - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); SystemClock.DateTimeOffsetUtcNow = () => fixedTime; // First execution should execute delegate and put result in the cache. cache.Execute(func, new Context(operationKey)).Should().Be(valueToReturn); delegateInvocations.Should().Be(1); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); @@ -140,15 +140,15 @@ public void Should_execute_delegate_but_not_put_value_in_cache_if_cache_does_not const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.Zero); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeFalse(); fromCache2.Should().BeNull(); } @@ -159,9 +159,9 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_prior_exe const string valueToReturn = "valueToReturn"; const string operationKey = "SomeOperationKey"; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; + int delegateInvocations = 0; Func func = _ => { delegateInvocations++; @@ -183,14 +183,14 @@ public void Should_allow_custom_FuncICacheKeyStrategy() { ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, context => context.OperationKey + context["id"]); object person1 = new ResultClass(ResultPrimitive.Good, "person1"); stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); object person2 = new ResultClass(ResultPrimitive.Good, "person2"); stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; + bool funcExecuted = false; Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); @@ -208,14 +208,14 @@ public void Should_allow_custom_ICacheKeyStrategy() ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); - var cache = Policy.Cache(stubCacheProvider.For(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); + CachePolicy cache = Policy.Cache(stubCacheProvider.For(), new RelativeTtl(TimeSpan.MaxValue), cacheKeyStrategy, emptyDelegate, emptyDelegate, emptyDelegate, noErrorHandling, noErrorHandling); object person1 = new ResultClass(ResultPrimitive.Good, "person1"); stubCacheProvider.Put("person1", person1, new Ttl(TimeSpan.MaxValue)); object person2 = new ResultClass(ResultPrimitive.Good, "person2"); stubCacheProvider.Put("person2", person2, new Ttl(TimeSpan.MaxValue)); - var funcExecuted = false; + bool funcExecuted = false; Func func = _ => { funcExecuted = true; return new ResultClass(ResultPrimitive.Fault, "should never return this one"); }; cache.Execute(func, new Context("person", new { id = "1" }.AsDictionary())).Should().BeSameAs(person1); @@ -236,15 +236,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -253,14 +253,14 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_reference_type() { ResultClass valueToReturnFromCache = default; - var valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); + ResultClass valueToReturnFromExecution = new ResultClass(ResultPrimitive.Good); const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; cache.Execute(_ => { @@ -279,15 +279,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } @@ -296,15 +296,15 @@ public void Should_execute_delegate_and_put_value_in_cache_if_cache_does_not_hol public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_holds_value__default_for_value_type() { ResultPrimitive valueToReturnFromCache = default; - var valueToReturnFromExecution = ResultPrimitive.Good; + ResultPrimitive valueToReturnFromExecution = ResultPrimitive.Good; valueToReturnFromExecution.Should().NotBe(valueToReturnFromCache); const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; cache.Execute(_ => { @@ -328,13 +328,13 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); Policy noop = Policy.NoOp(); - var wrap = cache.Wrap(noop); + PolicyWrap wrap = cache.Wrap(noop); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; wrap.Execute(_ => { @@ -354,13 +354,13 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); Policy noop = Policy.NoOp(); - var wrap = noop.Wrap(cache); + PolicyWrap wrap = noop.Wrap(cache); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; wrap.Execute(_ => { @@ -380,13 +380,13 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); Policy noop = Policy.NoOp(); - var wrap = Policy.Wrap(noop, cache, noop); + PolicyWrap wrap = Policy.Wrap(noop, cache, noop); stubCacheProvider.Put(operationKey, valueToReturnFromCache, new Ttl(TimeSpan.MaxValue)); - var delegateExecuted = false; + bool delegateExecuted = false; wrap.Execute(_ => { @@ -405,12 +405,12 @@ public void Should_return_value_from_cache_and_not_execute_delegate_if_cache_hol [Fact] public void Should_always_execute_delegate_if_execution_key_not_set() { - var valueToReturn = Guid.NewGuid().ToString(); + string valueToReturn = Guid.NewGuid().ToString(); - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var delegateInvocations = 0; - var func = () => + int delegateInvocations = 0; + Func func = () => { delegateInvocations++; return valueToReturn; @@ -433,11 +433,11 @@ public void Should_honour_cancellation_even_if_prior_execution_has_cached() const string valueToReturn = "valueToReturn"; const string operationKey = "SomeOperationKey"; - var cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(new StubCacheProvider(), TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); - var delegateInvocations = 0; + int delegateInvocations = 0; Func func = (_, _) => { // delegate does not observe cancellation token; test is whether CacheEngine does. @@ -462,9 +462,9 @@ public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_ const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - var tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource(); Func func = (_, ct) => { @@ -476,7 +476,7 @@ public void Should_honour_cancellation_during_delegate_execution_and_not_put_to_ cache.Invoking(policy => policy.Execute(func, new Context(operationKey), tokenSource.Token)) .Should().Throw(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); } diff --git a/src/Polly.Specs/Caching/ContextualTtlSpecs.cs b/src/Polly.Specs/Caching/ContextualTtlSpecs.cs index 8eceec5808c..0fc725606ba 100644 --- a/src/Polly.Specs/Caching/ContextualTtlSpecs.cs +++ b/src/Polly.Specs/Caching/ContextualTtlSpecs.cs @@ -17,22 +17,22 @@ public void Should_return_zero_if_no_value_set_on_context() [Fact] public void Should_return_zero_if_invalid_value_set_on_context() { - var contextData = new Dictionary(); + Dictionary contextData = new Dictionary(); contextData[ContextualTtl.TimeSpanKey] = new object(); - var context = new Context(String.Empty, contextData); + Context context = new Context(String.Empty, contextData); new ContextualTtl().GetTtl(context, null).Timespan.Should().Be(TimeSpan.Zero); } [Fact] public void Should_return_value_set_on_context() { - var ttl = TimeSpan.FromSeconds(30); - var contextData = new Dictionary(); + TimeSpan ttl = TimeSpan.FromSeconds(30); + Dictionary contextData = new Dictionary(); contextData[ContextualTtl.TimeSpanKey] = ttl; - var context = new Context(String.Empty, contextData); - var gotTtl = new ContextualTtl().GetTtl(context, null); + Context context = new Context(String.Empty, contextData); + Ttl gotTtl = new ContextualTtl().GetTtl(context, null); gotTtl.Timespan.Should().Be(ttl); gotTtl.SlidingExpiration.Should().BeFalse(); } @@ -40,12 +40,12 @@ public void Should_return_value_set_on_context() [Fact] public void Should_return_negative_value_set_on_context() { - var ttl = TimeSpan.FromTicks(-1); - var contextData = new Dictionary(); + TimeSpan ttl = TimeSpan.FromTicks(-1); + Dictionary contextData = new Dictionary(); contextData[ContextualTtl.TimeSpanKey] = ttl; - var context = new Context(String.Empty, contextData); - var gotTtl = new ContextualTtl().GetTtl(context, null); + Context context = new Context(String.Empty, contextData); + Ttl gotTtl = new ContextualTtl().GetTtl(context, null); gotTtl.Timespan.Should().Be(ttl); gotTtl.SlidingExpiration.Should().BeFalse(); } diff --git a/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs b/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs index 40857daf8c1..bb4205279d6 100644 --- a/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs +++ b/src/Polly.Specs/Caching/DefaultCacheKeyStrategySpecs.cs @@ -9,9 +9,9 @@ public class DefaultCacheKeyStrategySpecs [Fact] public void Should_return_Context_OperationKey_as_cache_key() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; - var context = new Context(operationKey); + Context context = new Context(operationKey); DefaultCacheKeyStrategy.Instance.GetCacheKey(context) .Should().Be(operationKey); diff --git a/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs b/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs index d3182905b5c..d573519f39e 100644 --- a/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/GenericCacheProviderAsyncSpecs.cs @@ -18,17 +18,17 @@ public async Task Should_not_error_for_executions_on_non_nullable_types_if_cache { const string operationKey = "SomeOperationKey"; - var onErrorCalled = false; + bool onErrorCalled = false; Action onError = (_, _, _) => { onErrorCalled = true; }; IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); - var result = await cache.ExecuteAsync(async _ => + ResultPrimitive result = await cache.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; return ResultPrimitive.Substitute; @@ -46,7 +46,7 @@ public async Task Should_execute_delegate_and_put_value_in_cache_for_non_nullabl IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); var cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit1, object fromCache1) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); @@ -56,7 +56,7 @@ public async Task Should_execute_delegate_and_put_value_in_cache_for_non_nullabl return ResultPrimitive.Substitute; }, new Context(operationKey))).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); + (bool cacheHit2, object fromCache2) = await stubCacheProvider.TryGetAsync(operationKey, CancellationToken.None, false); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); } diff --git a/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs b/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs index 8b6b457b6da..3ae137576cd 100644 --- a/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs +++ b/src/Polly.Specs/Caching/GenericCacheProviderSpecs.cs @@ -16,17 +16,17 @@ public void Should_not_error_for_executions_on_non_nullable_types_if_cache_does_ { const string operationKey = "SomeOperationKey"; - var onErrorCalled = false; + bool onErrorCalled = false; Action onError = (_, _, _) => { onErrorCalled = true; }; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(operationKey); cacheHit.Should().BeFalse(); fromCache.Should().BeNull(); - var result = cache.Execute(_ => ResultPrimitive.Substitute, new Context(operationKey)); + ResultPrimitive result = cache.Execute(_ => ResultPrimitive.Substitute, new Context(operationKey)); onErrorCalled.Should().BeFalse(); } @@ -38,16 +38,16 @@ public void Should_execute_delegate_and_put_value_in_cache_for_non_nullable_type const string operationKey = "SomeOperationKey"; ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); - var cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); - (var cacheHit1, var fromCache1) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit1, object fromCache1) = stubCacheProvider.TryGet(operationKey); cacheHit1.Should().BeFalse(); fromCache1.Should().BeNull(); cache.Execute(_ => valueToReturn, new Context(operationKey)).Should().Be(valueToReturn); - (var cacheHit2, var fromCache2) = stubCacheProvider.TryGet(operationKey); + (bool cacheHit2, object fromCache2) = stubCacheProvider.TryGet(operationKey); cacheHit2.Should().BeTrue(); fromCache2.Should().Be(valueToReturn); diff --git a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs index d1aa5aeb025..c2f4731ff75 100644 --- a/src/Polly.Specs/Caching/RelativeTtlSpecs.cs +++ b/src/Polly.Specs/Caching/RelativeTtlSpecs.cs @@ -35,11 +35,11 @@ public void Should_allow_timespan_max_value() [Fact] public void Should_return_configured_timespan() { - var ttl = TimeSpan.FromSeconds(30); + TimeSpan ttl = TimeSpan.FromSeconds(30); - var ttlStrategy = new RelativeTtl(ttl); + RelativeTtl ttlStrategy = new RelativeTtl(ttl); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); retrieved.Timespan.Should().BeCloseTo(ttl); retrieved.SlidingExpiration.Should().BeFalse(); } @@ -47,15 +47,15 @@ public void Should_return_configured_timespan() [Fact] public void Should_return_configured_timespan_from_time_requested() { - var fixedTime = SystemClock.DateTimeOffsetUtcNow(); - var ttl = TimeSpan.FromSeconds(30); - var delay = TimeSpan.FromSeconds(5); + DateTimeOffset fixedTime = SystemClock.DateTimeOffsetUtcNow(); + TimeSpan ttl = TimeSpan.FromSeconds(30); + TimeSpan delay = TimeSpan.FromSeconds(5); - var ttlStrategy = new RelativeTtl(ttl); + RelativeTtl ttlStrategy = new RelativeTtl(ttl); SystemClock.DateTimeOffsetUtcNow = () => fixedTime.Add(delay); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); retrieved.Timespan.Should().BeCloseTo(ttl); retrieved.SlidingExpiration.Should().BeFalse(); } diff --git a/src/Polly.Specs/Caching/ResultTtlSpecs.cs b/src/Polly.Specs/Caching/ResultTtlSpecs.cs index 4dff878c7c5..2ed9a2633f5 100644 --- a/src/Polly.Specs/Caching/ResultTtlSpecs.cs +++ b/src/Polly.Specs/Caching/ResultTtlSpecs.cs @@ -42,12 +42,12 @@ public void Should_not_throw_when_func_is_set_using_context() [Fact] public void Should_return_func_result() { - var ttl = TimeSpan.FromMinutes(1); + TimeSpan ttl = TimeSpan.FromMinutes(1); Func func = result => new Ttl(result.Ttl); - var ttlStrategy = new ResultTtl(func); + ResultTtl ttlStrategy = new ResultTtl(func); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }); + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }); retrieved.Timespan.Should().Be(ttl); retrieved.SlidingExpiration.Should().BeFalse(); } @@ -57,10 +57,10 @@ public void Should_return_func_result_using_context() { const string specialKey = "specialKey"; - var ttl = TimeSpan.FromMinutes(1); + TimeSpan ttl = TimeSpan.FromMinutes(1); Func func = (context, result) => context.OperationKey == specialKey ? new Ttl(TimeSpan.Zero) : new Ttl(result.Ttl); - var ttlStrategy = new ResultTtl(func); + ResultTtl ttlStrategy = new ResultTtl(func); ttlStrategy.GetTtl(new Context("someOperationKey"), new { Ttl = ttl }).Timespan.Should().Be(ttl); ttlStrategy.GetTtl(new Context(specialKey), new { Ttl = ttl }).Timespan.Should().Be(TimeSpan.Zero); diff --git a/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs b/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs index 2501ee74821..29c1da2341f 100644 --- a/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs +++ b/src/Polly.Specs/Caching/SerializingCacheProviderAsyncSpecs.cs @@ -16,7 +16,7 @@ public class AsyncSerializingCacheProviderSpecs [Fact] public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() { - var stubObjectSerializer = new StubSerializer( + StubSerializer stubObjectSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => s.Original ); @@ -48,21 +48,21 @@ public void Single_generic_extension_syntax_should_throw_on_no_serializer() [Fact] public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -72,21 +72,21 @@ public async Task Single_generic_SerializingCacheProvider_should_serialize_on_pu [Fact] public async Task Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); object objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -96,20 +96,20 @@ public async Task Single_generic_SerializingCacheProvider_should_serialize_on_pu [Fact] public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + object objectToCache = new object(); + string key = "some key"; await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -119,18 +119,18 @@ public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_ [Fact] public async Task Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - var serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + AsyncSerializingCacheProvider serializingCacheProvider = new AsyncSerializingCacheProvider(stubCacheProvider.AsyncFor(), stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); @@ -140,21 +140,21 @@ public async Task Single_generic_SerializingCacheProvider_should_not_deserialize [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -164,21 +164,21 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); object objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -188,19 +188,19 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + object objectToCache = new object(); + string key = "some key"; await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -210,18 +210,18 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - var serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + AsyncSerializingCacheProvider serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); @@ -235,7 +235,7 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ [Fact] public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() { - var stubTResultSerializer = new StubSerializer>( + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => s.Original ); @@ -267,21 +267,21 @@ public void Double_generic_extension_syntax_should_throw_on_no_serializer() [Fact] public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() @@ -291,21 +291,21 @@ public async Task Double_generic_SerializingCacheProvider_should_serialize_on_pu [Fact] public async Task Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); ResultPrimitive objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() @@ -315,19 +315,19 @@ public async Task Double_generic_SerializingCacheProvider_should_serialize_on_pu [Fact] public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - (var cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -337,18 +337,18 @@ public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_ [Fact] public async Task Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - var serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + AsyncSerializingCacheProvider> serializingCacheProvider = new AsyncSerializingCacheProvider>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); @@ -358,22 +358,22 @@ public async Task Double_generic_SerializingCacheProvider_should_not_deserialize [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = + AsyncSerializingCacheProvider> serializingCacheProvider = stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); + (bool cacheHit, object fromCache) = await stubCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() .Which.Original.Should().Be(objectToCache); @@ -382,22 +382,22 @@ public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_ [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); ResultPrimitive objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = + AsyncSerializingCacheProvider> serializingCacheProvider = stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() @@ -407,20 +407,20 @@ public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_ [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = + AsyncSerializingCacheProvider> serializingCacheProvider = stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -430,19 +430,19 @@ public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_ [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - var serializingCacheProvider = + AsyncSerializingCacheProvider> serializingCacheProvider = stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); - (var cacheHit, var fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); + (bool cacheHit, ResultPrimitive fromCache) = await serializingCacheProvider.TryGetAsync(key, CancellationToken.None, false); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); diff --git a/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs b/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs index d5cfc753ed9..5a1a198fb8f 100644 --- a/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs +++ b/src/Polly.Specs/Caching/SerializingCacheProviderSpecs.cs @@ -14,7 +14,7 @@ public class SerializingCacheProviderSpecs [Fact] public void Single_generic_constructor_should_throw_on_no_wrapped_cache_provider() { - var stubObjectSerializer = new StubSerializer( + StubSerializer stubObjectSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => s.Original ); @@ -46,21 +46,21 @@ public void Single_generic_extension_syntax_should_throw_on_no_serializer() [Fact] public void Single_generic_SerializingCacheProvider_should_serialize_on_put() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o);}, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -70,21 +70,21 @@ public void Single_generic_SerializingCacheProvider_should_serialize_on_put() [Fact] public void Single_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); object objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -94,20 +94,20 @@ public void Single_generic_SerializingCacheProvider_should_serialize_on_put_for_ [Fact] public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + object objectToCache = new object(); + string key = "some key"; stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -117,18 +117,18 @@ public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() [Fact] public void Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - var serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); @@ -138,21 +138,21 @@ public void Single_generic_SerializingCacheProvider_should_not_deserialize_on_ge [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = new object(); + string key = "some key"; - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -162,21 +162,21 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubSerializer = new StubSerializer( + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); object objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType() @@ -186,19 +186,19 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = new object(); - var key = "some key"; + object objectToCache = new object(); + string key = "some key"; stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -208,18 +208,18 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubSerializer = new StubSerializer( + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - var serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); @@ -233,7 +233,7 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should [Fact] public void Double_generic_constructor_should_throw_on_no_wrapped_cache_provider() { - var stubTResultSerializer = new StubSerializer>( + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => s.Original ); @@ -265,21 +265,21 @@ public void Double_generic_extension_syntax_should_throw_on_no_serializer() [Fact] public void Double_generic_SerializingCacheProvider_should_serialize_on_put() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() @@ -289,21 +289,21 @@ public void Double_generic_SerializingCacheProvider_should_serialize_on_put() [Fact] public void Double_generic_SerializingCacheProvider_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); ResultPrimitive objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() @@ -313,19 +313,19 @@ public void Double_generic_SerializingCacheProvider_should_serialize_on_put_for_ [Fact] public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - (var cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -335,18 +335,18 @@ public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() [Fact] public void Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item1.Should().BeFalse(); - var serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); @@ -356,22 +356,22 @@ public void Double_generic_SerializingCacheProvider_should_not_deserialize_on_ge [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = + SerializingCacheProvider> serializingCacheProvider = stubCacheProvider.For>().WithSerializer(stubTResultSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() @@ -381,23 +381,23 @@ public void Double_generic_SerializingCacheProvider_from_extension_syntax_should [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put_for_defaultTResult() { - var serializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, deserialize: s => s.Original ); - var stubCacheProvider = new StubCacheProvider(); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); ResultPrimitive objectToCache = default; - var key = "some key"; + string key = "some key"; - var serializingCacheProvider = + SerializingCacheProvider> serializingCacheProvider = stubCacheProvider.For>().WithSerializer(stubTResultSerializer); serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); serializeInvoked.Should().BeTrue(); - (var cacheHit, var fromCache) = stubCacheProvider.TryGet(key); + (bool cacheHit, object fromCache) = stubCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); fromCache.Should().BeOfType>() @@ -407,20 +407,20 @@ public void Double_generic_SerializingCacheProvider_from_extension_syntax_should [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var objectToCache = ResultPrimitive.Good; - var key = "some key"; + ResultPrimitive objectToCache = ResultPrimitive.Good; + string key = "some key"; - var serializingCacheProvider = + SerializingCacheProvider> serializingCacheProvider = stubCacheProvider.For>().WithSerializer(stubTResultSerializer); stubCacheProvider.Put(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1))); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeTrue(); deserializeInvoked.Should().BeTrue(); @@ -430,19 +430,19 @@ public void Double_generic_SerializingCacheProvider_from_extension_syntax_should [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() { - var deserializeInvoked = false; - var stubTResultSerializer = new StubSerializer>( + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( serialize: o => new StubSerialized(o), deserialize: s => { deserializeInvoked = true; return s.Original; } ); var stubCacheProvider = new StubCacheProvider(); - var key = "some key"; + string key = "some key"; stubCacheProvider.TryGet(key).Item2.Should().BeNull(); - var serializingCacheProvider = + SerializingCacheProvider> serializingCacheProvider = stubCacheProvider.For>().WithSerializer(stubTResultSerializer); - (var cacheHit, var fromCache) = serializingCacheProvider.TryGet(key); + (bool cacheHit, ResultPrimitive fromCache) = serializingCacheProvider.TryGet(key); cacheHit.Should().BeFalse(); deserializeInvoked.Should().BeFalse(); diff --git a/src/Polly.Specs/Caching/SlidingTtlSpecs.cs b/src/Polly.Specs/Caching/SlidingTtlSpecs.cs index 4ee9038ee08..1b726877c1a 100644 --- a/src/Polly.Specs/Caching/SlidingTtlSpecs.cs +++ b/src/Polly.Specs/Caching/SlidingTtlSpecs.cs @@ -34,11 +34,11 @@ public void Should_allow_timespan_max_value() [Fact] public void Should_return_configured_timespan() { - var ttl = TimeSpan.FromSeconds(30); + TimeSpan ttl = TimeSpan.FromSeconds(30); - var ttlStrategy = new SlidingTtl(ttl); + SlidingTtl ttlStrategy = new SlidingTtl(ttl); - var retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); + Ttl retrieved = ttlStrategy.GetTtl(new Context("someOperationKey"), null); retrieved.Timespan.Should().Be(ttl); retrieved.SlidingExpiration.Should().BeTrue(); } diff --git a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs index 01dad079089..f1817c01715 100644 --- a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs @@ -283,7 +283,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) .Should().Throw() .WithMessage("The circuit is now open and is not allowing calls.") @@ -1577,17 +1577,17 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). // The second execution should be rejected due to the halfopen state. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Awaiting(x => x.ExecuteAsync(async () => { @@ -1684,17 +1684,17 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Awaiting(x => x.ExecuteAsync(async () => { @@ -1777,7 +1777,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() breaker.CircuitState.Should().Be(CircuitState.Isolated); // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Isolated); @@ -1803,7 +1803,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time.Add(durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) .Should().Throw(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -1882,8 +1882,8 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi public void Should_not_call_onreset_on_initialise() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; var durationOfBreak = TimeSpan.FromSeconds(30); Policy @@ -1896,9 +1896,9 @@ public void Should_not_call_onreset_on_initialise() [Fact] public void Should_call_onbreak_when_breaking_circuit_automatically() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1939,9 +1939,9 @@ public void Should_call_onbreak_when_breaking_circuit_automatically() [Fact] public void Should_call_onbreak_when_breaking_circuit_manually() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var durationOfBreak = TimeSpan.FromSeconds(30); var breaker = Policy @@ -1958,9 +1958,9 @@ public void Should_call_onbreak_when_breaking_circuit_manually() [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -2011,9 +2011,9 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .Handle() @@ -2027,12 +2027,12 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub ); // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - var longRunningExecution = Task.Factory.StartNew(() => + Task longRunningExecution = Task.Factory.StartNew(() => { breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -2081,10 +2081,10 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -2140,8 +2140,8 @@ public async Task Should_call_onreset_when_automatically_closing_circuit_but_not public async Task Should_not_call_onreset_on_successive_successful_calls() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; var durationOfBreak = TimeSpan.FromSeconds(30); var breaker = Policy @@ -2162,12 +2162,12 @@ public async Task Should_not_call_onreset_on_successive_successful_calls() [Fact] public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -2224,12 +2224,12 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -2281,10 +2281,10 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onreset_when_manually_resetting_circuit() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -2361,7 +2361,7 @@ public void Should_call_onbreak_with_a_state_of_closed() Action onBreak = (_, state, _, _) => { transitionedState = state; }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; var breaker = Policy .Handle() @@ -2399,11 +2399,11 @@ public void Should_call_onbreak_with_a_state_of_closed() [Fact] public void Should_call_onbreak_with_a_state_of_half_open() { - var transitionedStates = new List(); + List transitionedStates = new List(); Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -2464,7 +2464,7 @@ public void Should_call_onbreak_with_the_correct_timespan() Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy .Handle() @@ -2635,7 +2635,7 @@ public async Task Should_call_onreset_with_the_passed_context() [Fact] public void Context_should_be_empty_if_execute_not_called_with_any_context_data() { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); Action onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; @@ -2834,12 +2834,12 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -2859,13 +2859,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -2888,13 +2888,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -2916,13 +2916,13 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -2944,12 +2944,12 @@ public void Should_report_faulting_from_faulting_action_execution_when_user_dele .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -2982,15 +2982,15 @@ public void Should_report_cancellation_when_both_open_circuit_and_cancellation() .WithInnerException(); // Circuit is now broken. - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; cancellationTokenSource.Cancel(); - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. @@ -3014,15 +3014,15 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; breaker.Awaiting(x => x.ExecuteAsync(async _ => { @@ -3044,15 +3044,15 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -3075,15 +3075,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs index c0855d4c65e..a565bd02f5a 100644 --- a/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs @@ -22,7 +22,7 @@ public class AdvancedCircuitBreakerSpecs : IDisposable [Fact] public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.MaxValue); @@ -151,7 +151,7 @@ public void Should_be_able_to_handle_a_duration_of_break_of_zero() [Fact] public void Should_initialise_to_closed_state() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); @@ -176,7 +176,7 @@ public void Should_not_open_circuit_if_failure_threshold_and_minimum_threshold_i var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -212,7 +212,7 @@ public void Should_not_open_circuit_if_exceptions_raised_are_not_one_of_the_spec var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .Or() .AdvancedCircuitBreaker( @@ -256,7 +256,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -284,7 +284,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) .Should().Throw() .WithMessage("The circuit is now open and is not allowing calls.") @@ -302,7 +302,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -347,7 +347,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -390,7 +390,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -435,7 +435,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -478,7 +478,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -525,7 +525,7 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -564,7 +564,7 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -603,7 +603,7 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -647,7 +647,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -690,7 +690,7 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_ var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -720,7 +720,7 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_ var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -756,7 +756,7 @@ public void Should_open_circuit_if_failures_at_end_of_last_timeslice_below_failu var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -807,7 +807,7 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_and_fai var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -855,7 +855,7 @@ public void Should_open_circuit_if_failures_in_second_window_of_last_timeslice_a var samplingDuration = TimeSpan.FromSeconds(10); var numberOfWindowsDefinedInCircuitBreaker = 10; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -910,7 +910,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -952,7 +952,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -993,7 +993,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1036,7 +1036,7 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput var samplingDuration = TimeSpan.FromMilliseconds(199); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1075,7 +1075,7 @@ public void Should_not_open_circuit_if_failure_threshold_exceeded_but_throughput var samplingDuration = TimeSpan.FromMilliseconds(199); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1114,7 +1114,7 @@ public void Should_open_circuit_with_the_last_raised_exception_if_failure_thresh var samplingDuration = TimeSpan.FromMilliseconds(199); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1157,7 +1157,7 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_and_throughput_ var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1187,7 +1187,7 @@ public void Should_not_open_circuit_if_failure_threshold_not_met_but_throughput_ var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1223,7 +1223,7 @@ public void Should_not_open_circuit_if_failures_at_end_of_last_timeslice_below_f var samplingDuration = TimeSpan.FromMilliseconds(199); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1279,7 +1279,7 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1323,7 +1323,7 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed_with var durationOfBreak = TimeSpan.FromSeconds(30); var samplingDuration = TimeSpan.FromSeconds(10); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1377,7 +1377,7 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1424,7 +1424,7 @@ public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_ var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1469,7 +1469,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1508,7 +1508,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1555,7 +1555,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1579,17 +1579,17 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). // The second execution should be rejected due to the halfopen state. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Invoking(x => x.Execute(() => { @@ -1609,7 +1609,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - var secondExecution = Task.Factory.StartNew(() => + Task secondExecution = Task.Factory.StartNew(() => { // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). firstExecutionActive.Should().BeTrue(); @@ -1660,7 +1660,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1685,17 +1685,17 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Invoking(x => x.Execute(() => { @@ -1715,7 +1715,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - var secondExecution = Task.Factory.StartNew(() => + Task secondExecution = Task.Factory.StartNew(() => { // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). firstExecutionActive.Should().BeTrue(); @@ -1766,7 +1766,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ [Fact] public void Should_open_circuit_and_block_calls_if_manual_override_open() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, TimeSpan.FromSeconds(30)); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -1776,7 +1776,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() breaker.CircuitState.Should().Be(CircuitState.Isolated); // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Isolated); @@ -1791,7 +1791,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -1802,7 +1802,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time.Add(durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) .Should().Throw(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -1815,7 +1815,7 @@ public void Should_close_circuit_again_on_reset_after_manual_override() SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -1838,7 +1838,7 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1879,8 +1879,8 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi public void Should_not_call_onreset_on_initialise() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; var durationOfBreak = TimeSpan.FromSeconds(30); Policy @@ -1893,14 +1893,14 @@ public void Should_not_call_onreset_on_initialise() [Fact] public void Should_call_onbreak_when_breaking_circuit_automatically() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -1936,12 +1936,12 @@ public void Should_call_onbreak_when_breaking_circuit_automatically() [Fact] public void Should_call_onbreak_when_breaking_circuit_manually() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); @@ -1955,14 +1955,14 @@ public void Should_call_onbreak_when_breaking_circuit_manually() [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2008,11 +2008,11 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2024,11 +2024,11 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub ); // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - var longRunningExecution = Task.Factory.StartNew(() => + Task longRunningExecution = Task.Factory.StartNew(() => { breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -2075,17 +2075,17 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2134,11 +2134,11 @@ public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_ public void Should_not_call_onreset_on_successive_successful_calls() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); @@ -2156,19 +2156,19 @@ public void Should_not_call_onreset_on_successive_successful_calls() [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2218,19 +2218,19 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2275,16 +2275,16 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onreset_when_manually_resetting_circuit() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak, onBreak, onReset); @@ -2314,7 +2314,7 @@ public void Should_call_onbreak_with_the_last_raised_exception() Action onBreak = (exception, _, _) => { passedException = exception; }; Action onReset = _ => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2351,9 +2351,9 @@ public void Should_call_onbreak_with_a_state_of_closed() Action onBreak = (_, state, _, _) => { transitionedState = state; }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2387,18 +2387,18 @@ public void Should_call_onbreak_with_a_state_of_closed() [Fact] public void Should_call_onbreak_with_a_state_of_half_open() { - var transitionedStates = new List(); + List transitionedStates = new List(); Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2450,9 +2450,9 @@ public void Should_call_onbreak_with_the_correct_timespan() Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromSeconds(30); + TimeSpan durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2492,7 +2492,7 @@ public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2526,7 +2526,7 @@ public void Should_call_onbreak_with_the_passed_context() var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2573,7 +2573,7 @@ public void Should_call_onreset_with_the_passed_context() var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2618,7 +2618,7 @@ public void Should_call_onreset_with_the_passed_context() [Fact] public void Context_should_be_empty_if_execute_not_called_with_any_context_data() { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); Action onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; @@ -2626,7 +2626,7 @@ public void Context_should_be_empty_if_execute_not_called_with_any_context_data( var time = 1.January(2000); SystemClock.UtcNow = () => time; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2673,7 +2673,7 @@ public void Should_create_new_context_for_each_call_to_execute() var durationOfBreak = TimeSpan.FromSeconds(30); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2723,7 +2723,7 @@ public void Should_create_new_context_for_each_call_to_execute() [Fact] public void Should_initialise_LastException_to_null_on_creation() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2738,7 +2738,7 @@ public void Should_initialise_LastException_to_null_on_creation() [Fact] public void Should_set_LastException_on_handling_exception_even_when_not_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2757,7 +2757,7 @@ public void Should_set_LastException_on_handling_exception_even_when_not_breakin [Fact] public void Should_set_LastException_to_last_raised_exception_when_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2780,7 +2780,7 @@ public void Should_set_LastException_to_last_raised_exception_when_breaking() [Fact] public void Should_set_LastException_to_null_on_circuit_reset() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, @@ -2812,16 +2812,16 @@ public void Should_set_LastException_to_null_on_circuit_reset() public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -2837,17 +2837,17 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -2866,17 +2866,17 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -2894,17 +2894,17 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -2922,16 +2922,16 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -2948,7 +2948,7 @@ public void Should_report_faulting_from_faulting_action_execution_when_user_dele public void Should_report_cancellation_when_both_open_circuit_and_cancellation() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 2, durationOfBreak); @@ -2964,15 +2964,15 @@ public void Should_report_cancellation_when_both_open_circuit_and_cancellation() .WithInnerException(); // Circuit is now broken. - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; cancellationTokenSource.Cancel(); - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. @@ -2992,19 +2992,19 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; breaker.Invoking(x => x.Execute(_ => { @@ -3021,19 +3021,19 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -3051,19 +3051,19 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance public void Should_honour_and_report_cancellation_during_func_execution() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 4, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs index 0457ad3ad73..375c71209cd 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs @@ -112,7 +112,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) .Should().Throw() .WithMessage("The circuit is now open and is not allowing calls.") @@ -139,7 +139,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) .Should().Throw() .WithMessage("The circuit is now open and is not allowing calls.") @@ -405,17 +405,17 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). // The second execution should be rejected due to the halfopen state. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Awaiting(x => x.ExecuteAsync(async () => { @@ -505,17 +505,17 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Awaiting(x => x.ExecuteAsync(async () => { @@ -603,7 +603,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() breaker.CircuitState.Should().Be(CircuitState.Isolated); // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Isolated); @@ -630,7 +630,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time.Add(durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return TaskHelper.EmptyTask; })) .Should().Throw(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -699,8 +699,8 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi public void Should_not_call_onreset_on_initialise() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; Policy .Handle() @@ -712,9 +712,9 @@ public void Should_not_call_onreset_on_initialise() [Fact] public void Should_call_onbreak_when_breaking_circuit_automatically() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .Handle() @@ -736,9 +736,9 @@ public void Should_call_onbreak_when_breaking_circuit_automatically() [Fact] public void Should_call_onbreak_when_breaking_circuit_manually() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .Handle() @@ -753,9 +753,9 @@ public void Should_call_onbreak_when_breaking_circuit_manually() [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .Handle() @@ -784,20 +784,20 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .Handle() .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - var longRunningExecution = Task.Factory.StartNew(() => + Task longRunningExecution = Task.Factory.StartNew(() => { breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -844,10 +844,10 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -891,8 +891,8 @@ public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_ public void Should_not_call_onreset_on_successive_successful_calls() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; var breaker = Policy .Handle() @@ -912,12 +912,12 @@ public void Should_not_call_onreset_on_successive_successful_calls() [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -958,12 +958,12 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -999,10 +999,10 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onreset_when_manually_resetting_circuit() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1061,7 +1061,7 @@ public void Should_call_onbreak_with_a_state_of_closed() Action onBreak = (_, state, _, _) => { transitionedState = state; }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; var breaker = Policy .Handle() @@ -1081,11 +1081,11 @@ public void Should_call_onbreak_with_a_state_of_closed() [Fact] public void Should_call_onbreak_with_a_state_of_half_open() { - var transitionedStates = new List(); + List transitionedStates = new List(); Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1132,7 +1132,7 @@ public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwra Action onBreak = (exception, _, _) => { passedException = exception; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy .HandleInner() @@ -1161,7 +1161,7 @@ public void Should_call_onbreak_with_the_correct_timespan() Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy .Handle() @@ -1267,7 +1267,7 @@ public async Task Should_call_onreset_with_the_passed_context() [Fact] public void Context_should_be_empty_if_execute_not_called_with_any_context_data() { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); Action onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; @@ -1426,12 +1426,12 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .Handle() .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -1451,13 +1451,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -1480,13 +1480,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -1508,13 +1508,13 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use .Handle() .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -1536,12 +1536,12 @@ public void Should_report_faulting_from_faulting_action_execution_when_user_dele .Handle() .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -1570,15 +1570,15 @@ public void Should_report_cancellation_when_both_open_circuit_and_cancellation() .WithInnerException(); // Circuit is now broken. - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; cancellationTokenSource.Cancel(); - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. @@ -1602,15 +1602,15 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act .Handle() .CircuitBreakerAsync(2, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; breaker.Awaiting(x => x.ExecuteAsync(async _ => { @@ -1632,15 +1632,15 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -1662,15 +1662,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs index 8701d795729..2b7a14c711f 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs @@ -19,7 +19,7 @@ public class CircuitBreakerSpecs : IDisposable [Fact] public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(1, TimeSpan.MaxValue); @@ -65,7 +65,7 @@ public void Should_initialise_to_closed_state() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); @@ -79,7 +79,7 @@ public void Should_initialise_to_closed_state() [Fact] public void Should_not_open_circuit_if_specified_number_of_specified_exception_are_not_raised_consecutively() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -98,7 +98,7 @@ public void Should_not_open_circuit_if_specified_number_of_specified_exception_a [Fact] public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_specified_exception_have_been_raised() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -110,7 +110,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Open); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) .Should().Throw() .WithMessage("The circuit is now open and is not allowing calls.") @@ -122,7 +122,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e [Fact] public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_exception_after_specified_number_of_one_of_the_specified_exceptions_have_been_raised() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .Or() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -136,7 +136,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e breaker.CircuitState.Should().Be(CircuitState.Open); // 2 exception raised, circuit is now open - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) .Should().Throw() .WithMessage("The circuit is now open and is not allowing calls.") @@ -148,7 +148,7 @@ public void Should_open_circuit_blocking_executions_and_noting_the_last_raised_e [Fact] public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exception() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -168,7 +168,7 @@ public void Should_not_open_circuit_if_exception_raised_is_not_the_specified_exc [Fact] public void Should_not_open_circuit_if_exception_raised_is_not_one_of_the_specified_exceptions() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .Or() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -198,7 +198,7 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); @@ -231,7 +231,7 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); @@ -269,7 +269,7 @@ public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_ var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); @@ -315,7 +315,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(1, durationOfBreak); @@ -346,7 +346,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(1, durationOfBreak); @@ -385,7 +385,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(1, durationOfBreak); @@ -402,17 +402,17 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). // The second execution should be rejected due to the halfopen state. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Invoking(x => x.Execute(() => { @@ -432,7 +432,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - var secondExecution = Task.Factory.StartNew(() => + Task secondExecution = Task.Factory.StartNew(() => { // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). firstExecutionActive.Should().BeTrue(); @@ -483,7 +483,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(1, durationOfBreak); @@ -501,17 +501,17 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Invoking(x => x.Execute(() => { @@ -531,7 +531,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - var secondExecution = Task.Factory.StartNew(() => + Task secondExecution = Task.Factory.StartNew(() => { // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). firstExecutionActive.Should().BeTrue(); @@ -587,7 +587,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -597,7 +597,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() breaker.CircuitState.Should().Be(CircuitState.Isolated); // circuit manually broken: execution should be blocked; even non-exception-throwing executions should not reset circuit - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => delegateExecutedWhenBroken = true)) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Isolated); @@ -613,7 +613,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -623,7 +623,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time.Add(durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) .Should().Throw(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -637,7 +637,7 @@ public void Should_close_circuit_again_on_reset_after_manual_override() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -660,7 +660,7 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); @@ -692,8 +692,8 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi public void Should_not_call_onreset_on_initialise() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; Policy .Handle() @@ -705,11 +705,11 @@ public void Should_not_call_onreset_on_initialise() [Fact] public void Should_call_onbreak_when_breaking_circuit_automatically() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -729,11 +729,11 @@ public void Should_call_onbreak_when_breaking_circuit_automatically() [Fact] public void Should_call_onbreak_when_breaking_circuit_manually() { - var onBreakCalled = false; + bool onBreakCalled = false; Action onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); onBreakCalled.Should().BeFalse(); @@ -746,11 +746,11 @@ public void Should_call_onbreak_when_breaking_circuit_manually() [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -777,20 +777,20 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - var longRunningExecution = Task.Factory.StartNew(() => + Task longRunningExecution = Task.Factory.StartNew(() => { breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -835,17 +835,17 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -882,10 +882,10 @@ public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_ public void Should_not_call_onreset_on_successive_successful_calls() { Action onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -903,19 +903,19 @@ public void Should_not_call_onreset_on_successive_successful_calls() [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); @@ -949,19 +949,19 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); @@ -990,17 +990,17 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onreset_when_manually_resetting_circuit() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1030,9 +1030,9 @@ public void Should_call_onbreak_with_the_last_raised_exception() Action onBreak = (exception, _, _) => { passedException = exception; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1054,9 +1054,9 @@ public void Should_call_onbreak_with_a_state_of_closed() Action onBreak = (_, state, _, _) => { transitionedState = state; }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); @@ -1074,18 +1074,18 @@ public void Should_call_onbreak_with_a_state_of_closed() [Fact] public void Should_call_onbreak_with_a_state_of_half_open() { - var transitionedStates = new List(); + List transitionedStates = new List(); Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; - var onHalfOpen = () => { }; + Action onHalfOpen = () => { }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset, onHalfOpen); @@ -1125,9 +1125,9 @@ public void Should_rethrow_and_call_onbreak_with_the_last_raised_exception_unwra Action onBreak = (exception, _, _) => { passedException = exception; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleInner() .Or() .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1154,9 +1154,9 @@ public void Should_call_onbreak_with_the_correct_timespan() Action onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1183,7 +1183,7 @@ public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -1207,7 +1207,7 @@ public void Should_call_onbreak_with_the_passed_context() Action onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -1238,7 +1238,7 @@ public void Should_call_onreset_with_the_passed_context() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1262,12 +1262,12 @@ public void Should_call_onreset_with_the_passed_context() [Fact] public void Context_should_be_empty_if_execute_not_called_with_any_context_data() { - var contextData = new {key1 = "value1", key2 = "value2"}.AsDictionary(); + IDictionary contextData = new {key1 = "value1", key2 = "value2"}.AsDictionary(); Action onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -1290,7 +1290,7 @@ public void Should_create_new_context_for_each_call_to_execute() Action onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -1339,7 +1339,7 @@ public void Should_initialise_LastException_to_null_on_creation() [Fact] public void Should_set_LastException_on_handling_exception_even_when_not_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1354,7 +1354,7 @@ public void Should_set_LastException_on_handling_exception_even_when_not_breakin [Fact] public void Should_set_LastException_on_handling_inner_exception_even_when_not_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleInner() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1373,7 +1373,7 @@ public void Should_set_LastException_on_handling_inner_exception_even_when_not_b [Fact] public void Should_set_LastException_to_last_raised_exception_when_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1391,7 +1391,7 @@ public void Should_set_LastException_to_last_raised_exception_when_breaking() [Fact] public void Should_set_LastException_to_null_on_circuit_reset() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1417,7 +1417,7 @@ public void Should_set_LastException_to_null_on_circuit_reset() [Fact] public void Should_set_PolicyResult_on_handling_inner_exception() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleInner() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1425,7 +1425,7 @@ public void Should_set_PolicyResult_on_handling_inner_exception() Exception toRaiseAsInner = new DivideByZeroException(); Exception withInner = new AggregateException(toRaiseAsInner); - var policyResult = breaker.ExecuteAndCapture(() => throw withInner); + PolicyResult policyResult = breaker.ExecuteAndCapture(() => throw withInner); policyResult.ExceptionType.Should().Be(ExceptionType.HandledByThisPolicy); policyResult.FinalException.Should().BeSameAs(toRaiseAsInner); @@ -1439,16 +1439,16 @@ public void Should_set_PolicyResult_on_handling_inner_exception() public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -1464,17 +1464,17 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -1493,17 +1493,17 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -1521,17 +1521,17 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -1549,16 +1549,16 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -1574,7 +1574,7 @@ public void Should_report_faulting_from_faulting_action_execution_when_user_dele [Fact] public void Should_report_cancellation_when_both_open_circuit_and_cancellation() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(1, TimeSpan.FromMinutes(1)); @@ -1587,15 +1587,15 @@ public void Should_report_cancellation_when_both_open_circuit_and_cancellation() .WithInnerException(); // Circuit is now broken. - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; cancellationTokenSource.Cancel(); - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. @@ -1615,19 +1615,19 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; breaker.Invoking(x => x.Execute(_ => { @@ -1644,19 +1644,19 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act public void Should_execute_func_returning_value_when_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -1673,19 +1673,19 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance [Fact] public void Should_honour_and_report_cancellation_during_func_execution() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs index 8e322a92a24..a8aa3d864d9 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultAsyncSpecs.cs @@ -467,17 +467,17 @@ public async Task Should_only_allow_single_execution_on_first_entering_halfopen_ // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). // The second execution should be rejected due to the halfopen state. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Awaiting(x => x.ExecuteAsync(async () => { @@ -571,17 +571,17 @@ public async Task Should_allow_single_execution_per_break_duration_in_halfopen_s // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Awaiting(x => x.ExecuteAsync(async () => { @@ -673,7 +673,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() breaker.CircuitState.Should().Be(CircuitState.Isolated); // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(b => b.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Isolated); @@ -701,7 +701,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time.Add(durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Awaiting(x => x.ExecuteAsync(() => { delegateExecutedWhenBroken = true; return Task.FromResult(ResultPrimitive.Good); })) .Should().Throw(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -770,8 +770,8 @@ public async Task Should_be_able_to_reset_automatically_opened_circuit_without_s public void Should_not_call_onreset_on_initialise() { Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; Policy .HandleResult(ResultPrimitive.Fault) @@ -783,9 +783,9 @@ public void Should_not_call_onreset_on_initialise() [Fact] public async Task Should_call_onbreak_when_breaking_circuit_automatically() { - var onBreakCalled = false; + bool onBreakCalled = false; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .HandleResult(ResultPrimitive.Fault) @@ -807,9 +807,9 @@ public async Task Should_call_onbreak_when_breaking_circuit_automatically() [Fact] public void Should_call_onbreak_when_breaking_circuit_manually() { - var onBreakCalled = false; + bool onBreakCalled = false; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .HandleResult(ResultPrimitive.Fault) @@ -824,9 +824,9 @@ public void Should_call_onbreak_when_breaking_circuit_manually() [Fact] public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .HandleResult(ResultPrimitive.Fault) @@ -855,18 +855,18 @@ public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_f [Fact] public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; var breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(1, TimeSpan.FromMinutes(1), onBreak, onReset); // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { Task longRunningExecution = Task.Factory.StartNew(async () => { @@ -915,10 +915,10 @@ public async Task Should_call_onbreak_when_breaking_circuit_first_time_but_not_f [Fact] public async Task Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -962,8 +962,8 @@ public async Task Should_call_onreset_when_automatically_closing_circuit_but_not public void Should_not_call_onreset_on_successive_successful_calls() { Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; var breaker = Policy .HandleResult(ResultPrimitive.Fault) @@ -983,12 +983,12 @@ public void Should_not_call_onreset_on_successive_successful_calls() [Fact] public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1030,12 +1030,12 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal [Fact] public async Task Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1071,10 +1071,10 @@ public async Task Should_call_onhalfopen_when_automatically_transitioning_to_hal [Fact] public void Should_call_onreset_when_manually_resetting_circuit() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; @@ -1112,7 +1112,7 @@ public async Task Should_call_onbreak_with_the_last_handled_result() Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy .HandleResult(ResultPrimitive.Fault) @@ -1137,7 +1137,7 @@ public async Task Should_call_onbreak_with_the_correct_timespan() Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); var breaker = Policy .HandleResult(ResultPrimitive.Fault) @@ -1245,7 +1245,7 @@ public async Task Should_call_onreset_with_the_passed_context() [Fact] public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; @@ -1392,13 +1392,13 @@ public async Task Should_execute_action_when_non_faulting_and_cancellationToken_ .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -1418,13 +1418,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. }; @@ -1449,13 +1449,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -1478,13 +1478,13 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -1507,13 +1507,13 @@ public async Task Should_report_faulting_from_faulting_action_execution_when_use .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -1540,15 +1540,15 @@ public async Task Should_report_cancellation_when_both_open_circuit_and_cancella .WithMessage("The circuit is now open and is not allowing calls."); // Circuit is now broken. - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; cancellationTokenSource.Cancel(); - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. ActionObservesCancellation = false @@ -1573,15 +1573,15 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(2, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; breaker.Awaiting(x => x.ExecuteAsync(async _ => { diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs index f9cde83bc9c..71151a00560 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultMixedResultExceptionSpecs.cs @@ -16,7 +16,7 @@ public class CircuitBreakerTResultMixedResultExceptionSpecs : IDisposable [Fact] public void Should_open_circuit_with_exception_after_specified_number_of_specified_exception_have_been_returned_when_result_policy_handling_exceptions_only() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -39,7 +39,7 @@ public void Should_open_circuit_with_exception_after_specified_number_of_specifi [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .Or() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -64,7 +64,7 @@ public void Should_open_circuit_with_the_last_exception_after_specified_number_o [Fact] public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_result_first() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .Or() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -89,7 +89,7 @@ public void Should_open_circuit_with_the_last_handled_result_after_specified_num [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -114,7 +114,7 @@ public void Should_open_circuit_with_the_last_exception_after_specified_number_o [Fact] public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__breaking_on_result__when_configuring_exception_first() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -139,7 +139,7 @@ public void Should_open_circuit_with_the_last_handled_result_after_specified_num [Fact] public void Should_open_circuit_if_results_and_exceptions_returned_match_combination_of_the_result_and_exception_predicates() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle(e => e.ParamName == "key") .OrResult(r => r.ResultCode == ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -164,7 +164,7 @@ public void Should_open_circuit_if_results_and_exceptions_returned_match_combina [Fact] public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_configured_results_or_exceptions() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -185,7 +185,7 @@ public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_configu [Fact] public void Should_not_open_circuit_if_exception_thrown_is_not_one_of_the_configured_results_or_exceptions() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -206,7 +206,7 @@ public void Should_not_open_circuit_if_exception_thrown_is_not_one_of_the_config [Fact] public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle(e => e.ParamName == "key") .OrResult(r => r.ResultCode == ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -240,7 +240,7 @@ public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_number_of_exceptions_and_results_have_been_raised__configuring_multiple_results_and_exceptions() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .Or() @@ -275,7 +275,7 @@ [Fact] public void Should_open_circuit_with_the_last_exception_after_specified_n [Fact] public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_exceptions_and_results_have_been_raised__when_configuring_multiple_results_and_exceptions() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .Or() @@ -310,7 +310,7 @@ public void Should_open_circuit_with_the_last_handled_result_after_specified_num [Fact] public void Should_not_open_circuit_if_result_raised_or_exception_thrown_is_not_one_of_the_handled_results_or_exceptions() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .Or() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -344,7 +344,7 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .Or() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -384,7 +384,7 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .Or() .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -430,9 +430,9 @@ public void Should_call_onbreak_with_the_last_handled_result() Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -456,9 +456,9 @@ public void Should_call_onbreak_with_the_last_raised_exception() Action, TimeSpan, Context> onBreak = (outcome, _, _) => { lastException = outcome.Exception; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -483,7 +483,7 @@ public void Should_call_onbreak_with_the_last_raised_exception() [Fact] public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -495,7 +495,7 @@ public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_cre [Fact] public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -512,7 +512,7 @@ public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaki [Fact] public void Should_set_LastException_on_exception_even_when_not_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -529,7 +529,7 @@ public void Should_set_LastException_on_exception_even_when_not_breaking() [Fact] public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -549,7 +549,7 @@ public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() [Fact] public void Should_set_LastException_to_last_exception_when_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -569,7 +569,7 @@ public void Should_set_LastException_to_last_exception_when_breaking() [Fact] public void Should_set_LastHandledResult_and_LastException_to_default_on_circuit_reset() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .Handle() .OrResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); diff --git a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs index 378d1f352af..9f0a1f49372 100644 --- a/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs +++ b/src/Polly.Specs/CircuitBreaker/CircuitBreakerTResultSpecs.cs @@ -21,7 +21,7 @@ public class CircuitBreakerTResultSpecs : IDisposable [Fact] public void Should_be_able_to_handle_a_duration_of_timespan_maxvalue() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, TimeSpan.MaxValue); @@ -67,7 +67,7 @@ public void Should_initialise_to_closed_state() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); @@ -81,7 +81,7 @@ public void Should_initialise_to_closed_state() [Fact] public void Should_not_open_circuit_if_specified_number_of_specified_handled_result_are_not_raised_consecutively() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -101,7 +101,7 @@ public void Should_not_open_circuit_if_specified_number_of_specified_handled_res [Fact] public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_have_been_returned() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -124,7 +124,7 @@ public void Should_open_circuit_with_the_last_handled_result_after_specified_num [Fact] public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_one_of_the_specified_handled_results_have_been_raised() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -148,7 +148,7 @@ public void Should_open_circuit_with_the_last_handled_result_after_specified_num [Fact] public void Should_open_circuit_with_the_last_handled_result_after_specified_number_of_specified_handled_result_with_predicate_have_been_returned() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -171,7 +171,7 @@ public void Should_open_circuit_with_the_last_handled_result_after_specified_num [Fact] public void Should_not_open_circuit_if_result_returned_is_not_the_handled_result() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -191,7 +191,7 @@ public void Should_not_open_circuit_if_result_returned_is_not_the_handled_result [Fact] public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled_results() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultYetAgain) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -212,7 +212,7 @@ public void Should_not_open_circuit_if_result_returned_is_not_one_of_the_handled [Fact] public void Should_not_open_circuit_if_result_returned_does_not_match_result_predicate() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -232,7 +232,7 @@ public void Should_not_open_circuit_if_result_returned_does_not_match_result_pre [Fact] public void Should_not_open_circuit_if_result_returned_does_not_match_any_of_the_result_predicates() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .OrResult(r => r.ResultCode == ResultPrimitive.FaultYetAgain) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -262,7 +262,7 @@ public void Should_halfopen_circuit_after_the_specified_duration_has_passed() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); @@ -295,7 +295,7 @@ public void Should_open_circuit_again_after_the_specified_duration_has_passed_if var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); @@ -334,7 +334,7 @@ public void Should_reset_circuit_after_the_specified_duration_has_passed_if_the_ var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); @@ -380,7 +380,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, durationOfBreak); @@ -411,7 +411,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, durationOfBreak); @@ -450,7 +450,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, durationOfBreak); @@ -467,17 +467,17 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Start one execution during the HalfOpen state, and request a second execution before the first has completed (ie still during the HalfOpen state). // The second execution should be rejected due to the halfopen state. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Invoking(x => x.Execute(() => { @@ -498,7 +498,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ // Attempt a second execution, signalled by the first execution to ensure they overlap: we should be able to verify it doesn't execute, and is rejected by a breaker in a HalfOpen state. permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - var secondExecution = Task.Factory.StartNew(() => + Task secondExecution = Task.Factory.StartNew(() => { // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). firstExecutionActive.Should().BeTrue(); @@ -550,7 +550,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, durationOfBreak); @@ -568,17 +568,17 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Request a second execution while the first is still in flight (not completed), while still during the HalfOpen state, but after one breakDuration later. // The second execution should be accepted in the halfopen state due to being requested after one breakDuration later. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitSecondExecutionAttempt = new ManualResetEvent(false)) - using (var permitFirstExecutionEnd = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitSecondExecutionAttempt = new ManualResetEvent(false)) + using (ManualResetEvent permitFirstExecutionEnd = new ManualResetEvent(false)) { bool? firstDelegateExecutedInHalfOpenState = null; bool? secondDelegateExecutedInHalfOpenState = null; bool? secondDelegateRejectedInHalfOpenState = null; - var firstExecutionActive = false; + bool firstExecutionActive = false; // First execution in HalfOpen state: we should be able to verify state is HalfOpen as it executes. - var firstExecution = Task.Factory.StartNew(() => + Task firstExecution = Task.Factory.StartNew(() => { breaker.Invoking(x => x.Execute(() => { @@ -599,7 +599,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ // Attempt a second execution, signalled by the first execution to ensure they overlap; start it one breakDuration later. We should be able to verify it does execute, though the breaker is still in a HalfOpen state. permitSecondExecutionAttempt.WaitOne(testTimeoutToExposeDeadlocks); - var secondExecution = Task.Factory.StartNew(() => + Task secondExecution = Task.Factory.StartNew(() => { // Validation of correct sequencing and overlapping of tasks in test (guard against erroneous test refactorings/operation). firstExecutionActive.Should().BeTrue(); @@ -657,7 +657,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -667,7 +667,7 @@ public void Should_open_circuit_and_block_calls_if_manual_override_open() breaker.CircuitState.Should().Be(CircuitState.Isolated); // circuit manually broken: execution should be blocked; even non-fault-returning executions should not reset circuit - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good;})) .Should().Throw(); breaker.CircuitState.Should().Be(CircuitState.Isolated); @@ -684,7 +684,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -695,7 +695,7 @@ public void Should_hold_circuit_open_despite_elapsed_time_if_manual_override_ope SystemClock.UtcNow = () => time.Add(durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Isolated); - var delegateExecutedWhenBroken = false; + bool delegateExecutedWhenBroken = false; breaker.Invoking(x => x.Execute(() => { delegateExecutedWhenBroken = true; return ResultPrimitive.Good; })) .Should().Throw(); delegateExecutedWhenBroken.Should().BeFalse(); @@ -709,7 +709,7 @@ public void Should_close_circuit_again_on_reset_after_manual_override() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -732,7 +732,7 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); @@ -764,8 +764,8 @@ public void Should_be_able_to_reset_automatically_opened_circuit_without_specifi public void Should_not_call_onreset_on_initialise() { Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; Policy .HandleResult(ResultPrimitive.Fault) @@ -777,11 +777,11 @@ public void Should_not_call_onreset_on_initialise() [Fact] public void Should_call_onbreak_when_breaking_circuit_automatically() { - var onBreakCalled = false; + bool onBreakCalled = false; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -801,11 +801,11 @@ public void Should_call_onbreak_when_breaking_circuit_automatically() [Fact] public void Should_call_onbreak_when_breaking_circuit_manually() { - var onBreakCalled = false; + bool onBreakCalled = false; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled = true; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); onBreakCalled.Should().BeFalse(); @@ -818,11 +818,11 @@ public void Should_call_onbreak_when_breaking_circuit_manually() [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_calls_placed_through_open_circuit() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -849,20 +849,20 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_subsequent_call_failure_which_arrives_on_open_state_though_started_on_closed_state() { - var onBreakCalled = 0; + int onBreakCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { }; + Action onReset = () => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, TimeSpan.FromMinutes(1), onBreak, onReset); // Start an execution when the breaker is in the closed state, but hold it from returning (its failure) until the breaker has opened. This call, a failure hitting an already open breaker, should indicate its fail, but should not cause onBreak() to be called a second time. - var testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); - using (var permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) - using (var permitMainThreadToOpenCircuit = new ManualResetEvent(false)) + TimeSpan testTimeoutToExposeDeadlocks = TimeSpan.FromSeconds(5); + using (ManualResetEvent permitLongRunningExecutionToReturnItsFailure = new ManualResetEvent(false)) + using (ManualResetEvent permitMainThreadToOpenCircuit = new ManualResetEvent(false)) { - var longRunningExecution = Task.Factory.StartNew(() => + Task longRunningExecution = Task.Factory.StartNew(() => { breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -907,17 +907,17 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub [Fact] public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_halfopen() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -954,10 +954,10 @@ public void Should_call_onreset_when_automatically_closing_circuit_but_not_when_ public void Should_not_call_onreset_on_successive_successful_calls() { Action, TimeSpan> onBreak = (_, _) => { }; - var onResetCalled = false; - var onReset = () => { onResetCalled = true; }; + bool onResetCalled = false; + Action onReset = () => { onResetCalled = true; }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -975,19 +975,19 @@ public void Should_not_call_onreset_on_successive_successful_calls() [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_subsequent_execution() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); @@ -1021,19 +1021,19 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_due_to_state_read() { - var onBreakCalled = 0; - var onResetCalled = 0; - var onHalfOpenCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; + int onHalfOpenCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; - var onHalfOpen = () => { onHalfOpenCalled++; }; + Action onReset = () => { onResetCalled++; }; + Action onHalfOpen = () => { onHalfOpenCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset, onHalfOpen); @@ -1062,17 +1062,17 @@ public void Should_call_onhalfopen_when_automatically_transitioning_to_halfopen_ [Fact] public void Should_call_onreset_when_manually_resetting_circuit() { - var onBreakCalled = 0; - var onResetCalled = 0; + int onBreakCalled = 0; + int onResetCalled = 0; Action, TimeSpan> onBreak = (_, _) => { onBreakCalled++; }; - var onReset = () => { onResetCalled++; }; + Action onReset = () => { onResetCalled++; }; var time = 1.January(2000); SystemClock.UtcNow = () => time; var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1102,9 +1102,9 @@ public void Should_call_onbreak_with_the_last_handled_result() Action, TimeSpan, Context> onBreak = (outcome, _, _) => { handledResult = outcome.Result; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1127,9 +1127,9 @@ public void Should_call_onbreak_with_the_correct_timespan() Action, TimeSpan, Context> onBreak = (_, timespan, _) => { passedBreakTimespan = timespan; }; Action onReset = _ => { }; - var durationOfBreak = TimeSpan.FromMinutes(1); + TimeSpan durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1156,7 +1156,7 @@ public void Should_open_circuit_with_timespan_maxvalue_if_manual_override_open() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); breaker.CircuitState.Should().Be(CircuitState.Closed); @@ -1180,7 +1180,7 @@ public void Should_call_onbreak_with_the_passed_context() Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -1211,7 +1211,7 @@ public void Should_call_onreset_with_the_passed_context() var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak, onBreak, onReset); @@ -1235,12 +1235,12 @@ public void Should_call_onreset_with_the_passed_context() [Fact] public void Context_should_be_empty_if_execute_not_called_with_any_context_data() { - var contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); + IDictionary contextData = new { key1 = "value1", key2 = "value2" }.AsDictionary(); Action, TimeSpan, Context> onBreak = (_, _, context) => { contextData = context; }; Action onReset = _ => { }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -1263,7 +1263,7 @@ public void Should_create_new_context_for_each_call_to_execute() Action, TimeSpan, Context> onBreak = (_, _, context) => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; Action onReset = context => { contextValue = context.ContainsKey("key") ? context["key"].ToString() : null; }; - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset); @@ -1302,7 +1302,7 @@ public void Should_create_new_context_for_each_call_to_execute() [Fact] public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_creation() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1313,7 +1313,7 @@ public void Should_initialise_LastHandledResult_and_LastResult_to_default_on_cre [Fact] public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1329,7 +1329,7 @@ public void Should_set_LastHandledResult_on_handling_result_even_when_not_breaki [Fact] public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1348,7 +1348,7 @@ public void Should_set_LastHandledResult_to_last_handled_result_when_breaking() [Fact] public void Should_set_LastHandledResult_to_default_on_circuit_reset() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, TimeSpan.FromMinutes(1)); @@ -1377,16 +1377,16 @@ public void Should_set_LastHandledResult_to_default_on_circuit_reset() public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -1402,17 +1402,17 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. }; @@ -1433,17 +1433,17 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -1462,17 +1462,17 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec public void Should_report_cancellation_during_faulting_action_execution_when_user_delegate_observes_cancellationToken() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -1491,16 +1491,16 @@ public void Should_report_cancellation_during_faulting_action_execution_when_use public void Should_report_faulting_from_faulting_action_execution_when_user_delegate_does_not_observe_cancellation() { var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -1515,7 +1515,7 @@ public void Should_report_faulting_from_faulting_action_execution_when_user_dele [Fact] public void Should_report_cancellation_when_both_open_circuit_and_cancellation() { - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, TimeSpan.FromMinutes(1)); @@ -1527,15 +1527,15 @@ public void Should_report_cancellation_when_both_open_circuit_and_cancellation() .WithMessage("The circuit is now open and is not allowing calls."); // Circuit is now broken. - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; cancellationTokenSource.Cancel(); - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancelled manually instead - see above. ActionObservesCancellation = false @@ -1556,19 +1556,19 @@ public void Should_honour_different_cancellationToken_captured_implicitly_by_act // Before CancellationToken support was built in to Polly, users of the library may have implicitly captured a CancellationToken and used it to cancel actions. For backwards compatibility, Polly should not confuse these with its own CancellationToken; it should distinguish OperationCanceledExceptions thrown with different CancellationTokens. var durationOfBreak = TimeSpan.FromMinutes(1); - var breaker = Policy + CircuitBreakerPolicy breaker = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(2, durationOfBreak); - var policyCancellationTokenSource = new CancellationTokenSource(); - var policyCancellationToken = policyCancellationTokenSource.Token; + CancellationTokenSource policyCancellationTokenSource = new CancellationTokenSource(); + CancellationToken policyCancellationToken = policyCancellationTokenSource.Token; - var implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); - var implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; + CancellationTokenSource implicitlyCapturedActionCancellationTokenSource = new CancellationTokenSource(); + CancellationToken implicitlyCapturedActionCancellationToken = implicitlyCapturedActionCancellationTokenSource.Token; implicitlyCapturedActionCancellationTokenSource.Cancel(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; breaker.Invoking(x => x.Execute(_ => { diff --git a/src/Polly.Specs/ContextSpecs.cs b/src/Polly.Specs/ContextSpecs.cs index 96ae781f25b..2c557fdcf3f 100644 --- a/src/Polly.Specs/ContextSpecs.cs +++ b/src/Polly.Specs/ContextSpecs.cs @@ -10,7 +10,7 @@ public class ContextSpecs [Fact] public void Should_assign_OperationKey_from_constructor() { - var context = new Context("SomeKey"); + Context context = new Context("SomeKey"); context.OperationKey.Should().Be("SomeKey"); @@ -20,7 +20,7 @@ public void Should_assign_OperationKey_from_constructor() [Fact] public void Should_assign_OperationKey_and_context_data_from_constructor() { - var context = new Context("SomeKey", new { key1 = "value1", key2 = "value2" }.AsDictionary()); + Context context = new Context("SomeKey", new { key1 = "value1", key2 = "value2" }.AsDictionary()); context.OperationKey.Should().Be("SomeKey"); context["key1"].Should().Be("value1"); @@ -30,7 +30,7 @@ public void Should_assign_OperationKey_and_context_data_from_constructor() [Fact] public void NoArgsCtor_should_assign_no_OperationKey() { - var context = new Context(); + Context context = new Context(); context.OperationKey.Should().BeNull(); } @@ -38,7 +38,7 @@ public void NoArgsCtor_should_assign_no_OperationKey() [Fact] public void Should_assign_CorrelationId_when_accessed() { - var context = new Context("SomeKey"); + Context context = new Context("SomeKey"); context.CorrelationId.Should().NotBeEmpty(); } @@ -46,10 +46,10 @@ public void Should_assign_CorrelationId_when_accessed() [Fact] public void Should_return_consistent_CorrelationId() { - var context = new Context("SomeKey"); + Context context = new Context("SomeKey"); - var retrieved1 = context.CorrelationId; - var retrieved2 = context.CorrelationId; + Guid retrieved1 = context.CorrelationId; + Guid retrieved2 = context.CorrelationId; retrieved1.Should().Be(retrieved2); } diff --git a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs index 905cc63bbdb..82da36f3d21 100644 --- a/src/Polly.Specs/Custom/CustomAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomAsyncSpecs.cs @@ -12,9 +12,9 @@ public class CustomAsyncSpecs [Fact] public void Should_be_able_to_construct_active_policy() { - var construct = () => + Action construct = () => { - var policy = AsyncPreExecutePolicy.CreateAsync(async () => + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(async () => { // Placeholder for more substantive async work. Console.WriteLine("Do something"); @@ -28,10 +28,10 @@ public void Should_be_able_to_construct_active_policy() [Fact] public void Active_policy_should_execute() { - var preExecuted = false; - var policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); + bool preExecuted = false; + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); - var executed = false; + bool executed = false; policy.Awaiting(x => x.ExecuteAsync(() => { executed = true; return Task.CompletedTask; })) .Should().NotThrow(); @@ -43,9 +43,9 @@ public void Active_policy_should_execute() [Fact] public void Should_be_able_to_construct_reactive_policy() { - var construct = () => + Action construct = () => { - var policy = Policy.Handle().WithBehaviourAsync(async ex => + AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { // Placeholder for more substantive async work. Console.WriteLine("Handling " + ex.Message); @@ -60,10 +60,10 @@ public void Should_be_able_to_construct_reactive_policy() public void Reactive_policy_should_handle_exception() { Exception handled = null; - var policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); + AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); Exception toThrow = new InvalidOperationException(); - var executed = false; + bool executed = false; policy.Awaiting(x => x.ExecuteAsync(() => { @@ -80,10 +80,10 @@ public void Reactive_policy_should_handle_exception() public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() { Exception handled = null; - var policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); + AsyncAddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviourAsync(async ex => { handled = ex; await Task.CompletedTask; }); Exception toThrow = new NotImplementedException(); - var executed = false; + bool executed = false; policy.Awaiting(x => x.ExecuteAsync(() => { diff --git a/src/Polly.Specs/Custom/CustomSpecs.cs b/src/Polly.Specs/Custom/CustomSpecs.cs index 86bfe8db9d8..c98bf0dae91 100644 --- a/src/Polly.Specs/Custom/CustomSpecs.cs +++ b/src/Polly.Specs/Custom/CustomSpecs.cs @@ -11,9 +11,9 @@ public class CustomSpecs [Fact] public void Should_be_able_to_construct_active_policy() { - var construct = () => + Action construct = () => { - var policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); + PreExecutePolicy policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); }; construct.Should().NotThrow(); @@ -22,10 +22,10 @@ public void Should_be_able_to_construct_active_policy() [Fact] public void Active_policy_should_execute() { - var preExecuted = false; - var policy = PreExecutePolicy.Create(() => preExecuted = true); + bool preExecuted = false; + PreExecutePolicy policy = PreExecutePolicy.Create(() => preExecuted = true); - var executed = false; + bool executed = false; policy.Invoking(x => x.Execute(() => { executed = true; })) .Should().NotThrow(); @@ -37,9 +37,9 @@ public void Active_policy_should_execute() [Fact] public void Should_be_able_to_construct_reactive_policy() { - var construct = () => + Action construct = () => { - var policy = Policy.Handle().WithBehaviour(ex => Console.WriteLine("Handling " + ex.Message)); + AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => Console.WriteLine("Handling " + ex.Message)); }; construct.Should().NotThrow(); @@ -49,10 +49,10 @@ public void Should_be_able_to_construct_reactive_policy() public void Reactive_policy_should_handle_exception() { Exception handled = null; - var policy = Policy.Handle().WithBehaviour(ex => handled = ex); + AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => handled = ex); Exception toThrow = new InvalidOperationException(); - var executed = false; + bool executed = false; policy.Invoking(x => x.Execute(() => { executed = true; @@ -68,10 +68,10 @@ public void Reactive_policy_should_handle_exception() public void Reactive_policy_should_be_able_to_ignore_unhandled_exception() { Exception handled = null; - var policy = Policy.Handle().WithBehaviour(ex => handled = ex); + AddBehaviourIfHandlePolicy policy = Policy.Handle().WithBehaviour(ex => handled = ex); Exception toThrow = new NotImplementedException(); - var executed = false; + bool executed = false; policy.Invoking(x => x.Execute(() => { executed = true; diff --git a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs index 4f5f88a603d..f48cfffd514 100644 --- a/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Custom/CustomTResultAsyncSpecs.cs @@ -13,9 +13,9 @@ public class CustomTResultAsyncSpecs [Fact] public void Should_be_able_to_construct_active_policy() { - var construct = () => + Action construct = () => { - var policy = AsyncPreExecutePolicy.CreateAsync(async () => + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(async () => { // Placeholder for more substantive async work. Console.WriteLine("Do something"); @@ -29,10 +29,10 @@ public void Should_be_able_to_construct_active_policy() [Fact] public void Active_policy_should_execute() { - var preExecuted = false; - var policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); + bool preExecuted = false; + AsyncPreExecutePolicy policy = AsyncPreExecutePolicy.CreateAsync(() => { preExecuted = true; return Task.CompletedTask; }); - var executed = false; + bool executed = false; policy.Awaiting(x => x.ExecuteAsync(async () => { executed = true; await Task.CompletedTask; return ResultPrimitive.Undefined; })) .Should().NotThrow(); @@ -43,9 +43,9 @@ public void Active_policy_should_execute() [Fact] public void Should_be_able_to_construct_reactive_policy() { - var construct = () => + Action construct = () => { - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviourAsync(async outcome => + AsyncAddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviourAsync(async outcome => { // Placeholder for more substantive async work. Console.WriteLine("Handling " + outcome.Result); @@ -59,13 +59,13 @@ public void Should_be_able_to_construct_reactive_policy() [Fact] public async Task Reactive_policy_should_handle_result() { - var handled = ResultPrimitive.Undefined; - var policy = Policy + ResultPrimitive handled = ResultPrimitive.Undefined; + AsyncAddBehaviourIfHandlePolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); - var toReturn = ResultPrimitive.Fault; - var executed = false; + ResultPrimitive toReturn = ResultPrimitive.Fault; + bool executed = false; (await policy.ExecuteAsync(async () => { @@ -83,12 +83,12 @@ public async Task Reactive_policy_should_handle_result() public async Task Reactive_policy_should_be_able_to_ignore_unhandled_result() { ResultPrimitive? handled = null; - var policy = Policy + AsyncAddBehaviourIfHandlePolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .WithBehaviourAsync(async outcome => { handled = outcome.Result; await Task.CompletedTask; }); - var toReturn = ResultPrimitive.FaultYetAgain; - var executed = false; + ResultPrimitive toReturn = ResultPrimitive.FaultYetAgain; + bool executed = false; (await policy.ExecuteAsync(async () => { diff --git a/src/Polly.Specs/Custom/CustomTResultSpecs.cs b/src/Polly.Specs/Custom/CustomTResultSpecs.cs index 43ae11be96e..f7f43bedf9a 100644 --- a/src/Polly.Specs/Custom/CustomTResultSpecs.cs +++ b/src/Polly.Specs/Custom/CustomTResultSpecs.cs @@ -12,9 +12,9 @@ public class CustomTResultSpecs [Fact] public void Should_be_able_to_construct_active_policy() { - var construct = () => + Action construct = () => { - var policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); + PreExecutePolicy policy = PreExecutePolicy.Create(() => Console.WriteLine("Do something")); }; construct.Should().NotThrow(); @@ -23,10 +23,10 @@ public void Should_be_able_to_construct_active_policy() [Fact] public void Active_policy_should_execute() { - var preExecuted = false; - var policy = PreExecutePolicy.Create(() => preExecuted = true); + bool preExecuted = false; + PreExecutePolicy policy = PreExecutePolicy.Create(() => preExecuted = true); - var executed = false; + bool executed = false; policy.Invoking(x => x.Execute(() => { executed = true; @@ -41,9 +41,9 @@ public void Active_policy_should_execute() [Fact] public void Should_be_able_to_construct_reactive_policy() { - var construct = () => + Action construct = () => { - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => Console.WriteLine("Handling " + outcome.Result)); + AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => Console.WriteLine("Handling " + outcome.Result)); }; construct.Should().NotThrow(); @@ -52,11 +52,11 @@ public void Should_be_able_to_construct_reactive_policy() [Fact] public void Reactive_policy_should_handle_result() { - var handled = ResultPrimitive.Undefined; - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); + ResultPrimitive handled = ResultPrimitive.Undefined; + AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); - var toReturn = ResultPrimitive.Fault; - var executed = false; + ResultPrimitive toReturn = ResultPrimitive.Fault; + bool executed = false; policy.Execute(() => { @@ -73,10 +73,10 @@ public void Reactive_policy_should_handle_result() public void Reactive_policy_should_be_able_to_ignore_unhandled_result() { ResultPrimitive? handled = null; - var policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); + AddBehaviourIfHandlePolicy policy = Policy.HandleResult(ResultPrimitive.Fault).WithBehaviour(outcome => handled = outcome.Result); - var toReturn = ResultPrimitive.FaultYetAgain; - var executed = false; + ResultPrimitive toReturn = ResultPrimitive.FaultYetAgain; + bool executed = false; policy.Execute(() => { diff --git a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs index d7b509a4be1..a71db753b4a 100644 --- a/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackAsyncSpecs.cs @@ -91,7 +91,7 @@ public void Should_throw_when_onFallback_delegate_is_null_with_context() [Fact] public async Task Should_not_execute_fallback_when_executed_delegate_does_not_throw() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -106,7 +106,7 @@ public async Task Should_not_execute_fallback_when_executed_delegate_does_not_th [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -121,7 +121,7 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_exception_ [Fact] public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -137,7 +137,7 @@ public void Should_execute_fallback_when_executed_delegate_throws_exception_hand [Fact] public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -154,7 +154,7 @@ public void Should_execute_fallback_when_executed_delegate_throws_one_of_excepti [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -170,7 +170,7 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_exception_ [Fact] public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -185,7 +185,7 @@ public void Should_not_execute_fallback_when_exception_thrown_does_not_match_han [Fact] public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -201,7 +201,7 @@ public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any [Fact] public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -217,7 +217,7 @@ public void Should_execute_fallback_when_exception_thrown_matches_handling_predi [Fact] public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -233,7 +233,7 @@ public void Should_execute_fallback_when_exception_thrown_matches_one_of_handlin [Fact] public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; @@ -267,7 +267,7 @@ public void Should_throw_for_generic_method_execution_on_non_generic_policy() [Fact] public async Task Should_call_onFallback_passing_exception_triggering_fallback() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; Exception exceptionPassedToOnFallback = null; @@ -290,7 +290,7 @@ public async Task Should_not_call_onFallback_when_executed_delegate_does_not_thr { Func fallbackActionAsync = _ => TaskHelper.EmptyTask; - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Func onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -382,7 +382,7 @@ public void Should_call_onFallback_with_independent_context_for_independent_call public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Func fallbackActionAsync = (_, _) => TaskHelper.EmptyTask; Func onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; @@ -446,7 +446,7 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var fallbackExecuted = false; + bool fallbackExecuted = false; Func fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; @@ -521,7 +521,7 @@ public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrappe .FallbackAsync(fallbackFunc, onFallback); Exception instanceToCapture = new ArgumentNullException("myParam"); - var instanceToThrow = new Exception(String.Empty, instanceToCapture); + Exception instanceToThrow = new Exception(String.Empty, instanceToCapture); fallbackPolicy.Awaiting(p => p.RaiseExceptionAsync(instanceToThrow)) .Should().NotThrow(); @@ -575,20 +575,20 @@ public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhan [Fact] public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var policy = Policy .Handle() .FallbackAsync(fallbackActionAsync); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -604,20 +604,20 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca [Fact] public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var policy = Policy .Handle() .FallbackAsync(fallbackActionAsync); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, @@ -633,20 +633,20 @@ public void Should_execute_fallback_when_faulting_and_cancellationToken_not_canc [Fact] public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var policy = Policy .Handle() .FallbackAsync(fallbackActionAsync); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -666,20 +666,20 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex [Fact] public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var policy = Policy .Handle() .FallbackAsync(fallbackActionAsync); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -697,7 +697,7 @@ public void Should_report_cancellation_and_not_execute_fallback_during_otherwise [Fact] public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var policy = Policy @@ -705,13 +705,13 @@ public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non .Or() .FallbackAsync(fallbackActionAsync); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -729,20 +729,20 @@ public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; var policy = Policy .Handle() .FallbackAsync(fallbackActionAsync); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -760,21 +760,21 @@ public void Should_not_report_cancellation_and_not_execute_fallback_if_non_fault public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() .FallbackAsync(fallbackActionAsync); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -792,21 +792,21 @@ public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_exe public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func fallbackActionAsync = _ => { fallbackActionExecuted = true; return TaskHelper.EmptyTask; }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() .FallbackAsync(fallbackActionAsync); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Fallback/FallbackSpecs.cs b/src/Polly.Specs/Fallback/FallbackSpecs.cs index bdda7d69bde..de3f4d28856 100644 --- a/src/Polly.Specs/Fallback/FallbackSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackSpecs.cs @@ -99,7 +99,7 @@ public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onF [Fact] public void Should_throw_when_onFallback_delegate_is_null() { - var fallbackAction = () => { }; + Action fallbackAction = () => { }; Action onFallback = null; Action policy = () => Policy @@ -159,10 +159,10 @@ public void Should_throw_when_onFallback_delegate_is_null_with_context_with_acti [Fact] public void Should_not_execute_fallback_when_executed_delegate_does_not_throw() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction); @@ -174,10 +174,10 @@ public void Should_not_execute_fallback_when_executed_delegate_does_not_throw() [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction); @@ -190,10 +190,10 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_exception_ [Fact] public void Should_execute_fallback_when_executed_delegate_throws_exception_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction); @@ -205,10 +205,10 @@ public void Should_execute_fallback_when_executed_delegate_throws_exception_hand [Fact] public void Should_execute_fallback_when_executed_delegate_throws_one_of_exceptions_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Or() .Fallback(fallbackAction); @@ -221,10 +221,10 @@ public void Should_execute_fallback_when_executed_delegate_throws_one_of_excepti [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_exception_not_one_of_exceptions_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Or() .Fallback(fallbackAction); @@ -237,10 +237,10 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_exception_ [Fact] public void Should_not_execute_fallback_when_exception_thrown_does_not_match_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle(_ => false) .Fallback(fallbackAction); @@ -252,10 +252,10 @@ public void Should_not_execute_fallback_when_exception_thrown_does_not_match_han [Fact] public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any_of_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle(_ => false) .Or(_ => false) .Fallback(fallbackAction); @@ -268,10 +268,10 @@ public void Should_not_execute_fallback_when_exception_thrown_does_not_match_any [Fact] public void Should_execute_fallback_when_exception_thrown_matches_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle(_ => true) .Fallback(fallbackAction); @@ -283,10 +283,10 @@ public void Should_execute_fallback_when_exception_thrown_matches_handling_predi [Fact] public void Should_execute_fallback_when_exception_thrown_matches_one_of_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle(_ => true) .Or() .Fallback(fallbackAction); @@ -299,14 +299,14 @@ public void Should_execute_fallback_when_exception_thrown_matches_one_of_handlin [Fact] public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_exception_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; throw new DivideByZeroException {HelpLink = "FromFallbackAction"}; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction); @@ -319,7 +319,7 @@ public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_e [Fact] public void Should_throw_for_generic_method_execution_on_non_generic_policy() { - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(() => {}); @@ -333,14 +333,14 @@ public void Should_throw_for_generic_method_execution_on_non_generic_policy() [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_where_policy_doesnt_handle_inner() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); @@ -350,10 +350,10 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exce [Fact] public void Should_execute_fallback_when_policy_handles_inner_and_executed_delegate_throws_as_non_inner() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); @@ -367,14 +367,14 @@ public void Should_execute_fallback_when_policy_handles_inner_and_executed_deleg [Fact] public void Should_execute_fallback_when_executed_delegate_throws_inner_exception_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); @@ -384,15 +384,15 @@ public void Should_execute_fallback_when_executed_delegate_throws_inner_exceptio [Fact] public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_exceptions_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .OrInner() .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentException()); + Exception withInner = new Exception(String.Empty, new ArgumentException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); @@ -402,14 +402,14 @@ public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_e [Fact] public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_exception_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); + Exception withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); @@ -420,14 +420,14 @@ public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_e [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exception_not_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentException()); + Exception withInner = new Exception(String.Empty, new ArgumentException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); @@ -437,14 +437,14 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_inner_exce [Fact] public void Should_execute_fallback_when_inner_exception_thrown_matches_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => true) .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); @@ -454,14 +454,14 @@ public void Should_execute_fallback_when_inner_exception_thrown_matches_handling [Fact] public void Should_execute_fallback_when_inner_nested_exception_thrown_matches_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => true) .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); + Exception withInner = new Exception(String.Empty, new Exception(String.Empty, new Exception(String.Empty, new DivideByZeroException()))); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); @@ -472,15 +472,15 @@ public void Should_execute_fallback_when_inner_nested_exception_thrown_matches_h [Fact] public void Should_execute_fallback_when_inner_exception_thrown_matches_one_of_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => true) .OrInner(_ => true) .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentNullException()); + Exception withInner = new Exception(String.Empty, new ArgumentNullException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().NotThrow(); @@ -490,14 +490,14 @@ public void Should_execute_fallback_when_inner_exception_thrown_matches_one_of_h [Fact] public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => false) .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new DivideByZeroException()); + Exception withInner = new Exception(String.Empty, new DivideByZeroException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); @@ -507,15 +507,15 @@ public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_mat [Fact] public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_match_any_of_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => false) .OrInner(_ => false) .Fallback(fallbackAction); - var withInner = new Exception(String.Empty, new ArgumentNullException()); + Exception withInner = new Exception(String.Empty, new ArgumentNullException()); fallbackPolicy.Invoking(x => x.RaiseException(withInner)).Should().Throw().And.InnerException.Should().BeOfType(); @@ -530,10 +530,10 @@ public void Should_not_execute_fallback_when_inner_exception_thrown_does_not_mat [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_where_policy_doesnt_handle_inner() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction); @@ -547,10 +547,10 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_a [Fact] public void Should_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); @@ -564,10 +564,10 @@ public void Should_execute_fallback_when_executed_delegate_throws_inner_of_aggre [Fact] public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_of_aggregate_exceptions_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .OrInner() .Fallback(fallbackAction); @@ -582,10 +582,10 @@ public void Should_execute_fallback_when_executed_delegate_throws_one_of_inner_o [Fact] public void Should_execute_fallback_when_executed_delegate_throws_aggregate_exception_with_inner_handled_by_policy_amongst_other_inners() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); @@ -599,10 +599,10 @@ public void Should_execute_fallback_when_executed_delegate_throws_aggregate_exce [Fact] public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_of_aggregate_exception_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); @@ -616,10 +616,10 @@ public void Should_execute_fallback_when_executed_delegate_throws_nested_inner_o [Fact] public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_aggregate_exception_not_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction); @@ -633,10 +633,10 @@ public void Should_not_execute_fallback_when_executed_delegate_throws_inner_of_a [Fact] public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => true) .Fallback(fallbackAction); @@ -650,10 +650,10 @@ public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_mat [Fact] public void Should_execute_fallback_when_inner_of_aggregate_nested_exception_thrown_matches_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => true) .Fallback(fallbackAction); @@ -668,10 +668,10 @@ public void Should_execute_fallback_when_inner_of_aggregate_nested_exception_thr [Fact] public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_matches_one_of_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => true) .OrInner(_ => true) .Fallback(fallbackAction); @@ -686,10 +686,10 @@ public void Should_execute_fallback_when_inner_of_aggregate_exception_thrown_mat [Fact] public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => false) .Fallback(fallbackAction); @@ -703,10 +703,10 @@ public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown [Fact] public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown_does_not_match_any_of_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner(_ => false) .OrInner(_ => false) .Fallback(fallbackAction); @@ -725,13 +725,13 @@ public void Should_not_execute_fallback_when_inner_of_aggregate_exception_thrown [Fact] public void Should_call_onFallback_passing_exception_triggering_fallback() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; Exception exceptionPassedToOnFallback = null; Action onFallback = ex => { exceptionPassedToOnFallback = ex; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -746,12 +746,12 @@ public void Should_call_onFallback_passing_exception_triggering_fallback() [Fact] public void Should_not_call_onFallback_when_executed_delegate_does_not_throw() { - var fallbackAction = () => { }; + Action fallbackAction = () => { }; - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Action onFallback = _ => { onFallbackExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -773,7 +773,7 @@ public void Should_call_onFallback_with_the_passed_context() Action onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -795,7 +795,7 @@ public void Should_call_onFallback_with_the_passed_context_when_execute_and_capt Action onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -817,7 +817,7 @@ public void Should_call_onFallback_with_independent_context_for_independent_call Action onFallback = (ex, ctx) => { contextData[ex.GetType()] = ctx["key"]; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Or() .Fallback(fallbackAction, onFallback); @@ -842,12 +842,12 @@ public void Should_call_onFallback_with_independent_context_for_independent_call public void Context_should_be_empty_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Action fallbackAction = _ => { }; Action onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Or() .Fallback(fallbackAction, onFallback); @@ -867,7 +867,7 @@ public void Should_call_fallbackAction_with_the_passed_context() Action onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -889,7 +889,7 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ Action onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -906,12 +906,12 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var fallbackExecuted = false; + bool fallbackExecuted = false; Action fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; }; Action onFallback = (_, _) => {}; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Or() .Fallback(fallbackAction, onFallback); @@ -935,7 +935,7 @@ public void Should_call_fallbackAction_with_the_exception() Action onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -955,7 +955,7 @@ public void Should_call_fallbackAction_with_the_exception_when_execute_and_captu Action onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); fallbackPolicy.Invoking(p => p.ExecuteAndCapture(() => throw new ArgumentNullException())) @@ -974,12 +974,12 @@ public void Should_call_fallbackAction_with_the_matched_inner_exception_unwrappe Action onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction, onFallback); Exception instanceToCapture = new ArgumentNullException("myParam"); - var instanceToThrow = new Exception(String.Empty, instanceToCapture); + Exception instanceToThrow = new Exception(String.Empty, instanceToCapture); fallbackPolicy.Invoking(p => p.RaiseException(instanceToThrow)) .Should().NotThrow(); @@ -995,7 +995,7 @@ public void Should_call_fallbackAction_with_the_matched_inner_of_aggregate_excep Action onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleInner() .Fallback(fallbackAction, onFallback); @@ -1017,7 +1017,7 @@ public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhan Action onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .Handle() .Fallback(fallbackAction, onFallback); @@ -1034,19 +1034,19 @@ public void Should_not_call_fallbackAction_with_the_exception_if_exception_unhan [Fact] public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -1062,19 +1062,19 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca [Fact] public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, @@ -1090,20 +1090,20 @@ public void Should_execute_fallback_when_faulting_and_cancellationToken_not_canc [Fact] public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -1123,20 +1123,20 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex [Fact] public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -1154,20 +1154,20 @@ public void Should_report_cancellation_and_not_execute_fallback_during_otherwise [Fact] public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Or() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -1184,19 +1184,19 @@ public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non [Fact] public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -1213,19 +1213,19 @@ public void Should_not_report_cancellation_and_not_execute_fallback_if_non_fault [Fact] public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, @@ -1242,19 +1242,19 @@ public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_exe [Fact] public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; }; + bool fallbackActionExecuted = false; + Action fallbackAction = () => { fallbackActionExecuted = true; }; - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var policy = Policy + FallbackPolicy policy = Policy .Handle() .Fallback(fallbackAction); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs index bc8442bbb31..5c88b6fa565 100644 --- a/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackTResultAsyncSpecs.cs @@ -119,7 +119,7 @@ public void Should_throw_when_onFallback_delegate_is_null_with_context_with_acti [Fact] public async Task Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -134,7 +134,7 @@ public async Task Should_not_execute_fallback_when_executed_delegate_does_not_ra [Fact] public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -161,7 +161,7 @@ public async Task Should_return_fallback_value_when_executed_delegate_raises_fau [Fact] public async Task Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -177,7 +177,7 @@ public async Task Should_execute_fallback_when_executed_delegate_raises_fault_ha [Fact] public async Task Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -194,7 +194,7 @@ public async Task Should_execute_fallback_when_executed_delegate_raises_one_of_r [Fact] public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -211,7 +211,7 @@ public async Task Should_not_execute_fallback_when_executed_delegate_raises_faul [Fact] public async Task Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -227,7 +227,7 @@ public async Task Should_not_execute_fallback_when_result_raised_does_not_match_ [Fact] public async Task Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -244,7 +244,7 @@ public async Task Should_not_execute_fallback_when_executed_delegate_raises_faul [Fact] public async Task Should_execute_fallback_when_result_raised_matches_handling_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -261,7 +261,7 @@ public async Task Should_execute_fallback_when_result_raised_matches_handling_pr [Fact] public async Task Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var fallbackPolicy = Policy @@ -278,7 +278,7 @@ public async Task Should_execute_fallback_when_result_raised_matches_one_of_hand [Fact] public async Task Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; @@ -302,7 +302,7 @@ public async Task Should_not_handle_result_raised_by_fallback_delegate_even_if_i [Fact] public async Task Should_call_onFallback_passing_result_triggering_fallback() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(new ResultClass(ResultPrimitive.Substitute)); }; ResultClass resultPassedToOnFallback = null; @@ -312,7 +312,7 @@ public async Task Should_call_onFallback_passing_result_triggering_fallback() .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .FallbackAsync(fallbackAction, onFallbackAsync); - var resultFromDelegate = new ResultClass(ResultPrimitive.Fault); + ResultClass resultFromDelegate = new ResultClass(ResultPrimitive.Fault); await fallbackPolicy.ExecuteAsync(() => Task.FromResult(resultFromDelegate)); fallbackActionExecuted.Should().BeTrue(); @@ -325,7 +325,7 @@ public async Task Should_not_call_onFallback_when_executed_delegate_does_not_rai { Func> fallbackAction = _ => Task.FromResult(ResultPrimitive.Substitute); - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Func, Task> onFallbackAsync = _ => { onFallbackExecuted = true; return TaskHelper.EmptyTask; }; var fallbackPolicy = Policy @@ -419,7 +419,7 @@ public void Should_call_onFallback_with_independent_context_for_independent_call public async Task Context_should_be_empty_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Func> fallbackAction = (_, _) => Task.FromResult(ResultPrimitive.Substitute); Func, Context, Task> onFallbackAsync = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; return TaskHelper.EmptyTask; }; @@ -485,7 +485,7 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ public async Task Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var fallbackExecuted = false; + bool fallbackExecuted = false; Func> fallbackActionAsync = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return Task.FromResult(ResultPrimitive.Substitute); }; @@ -580,7 +580,7 @@ public async Task Should_not_call_fallbackAction_with_the_fault_if_fault_unhandl [Fact] public async Task Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var policy = Policy @@ -588,13 +588,13 @@ public async Task Should_execute_action_when_non_faulting_and_cancellationToken_ .OrResult(ResultPrimitive.FaultAgain) .FallbackAsync(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -609,7 +609,7 @@ public async Task Should_execute_action_when_non_faulting_and_cancellationToken_ [Fact] public async Task Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var policy = Policy @@ -617,13 +617,13 @@ public async Task Should_execute_fallback_when_faulting_and_cancellationToken_no .OrResult(ResultPrimitive.FaultAgain) .FallbackAsync(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -638,7 +638,7 @@ public async Task Should_execute_fallback_when_faulting_and_cancellationToken_no [Fact] public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var policy = Policy @@ -646,13 +646,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .OrResult(ResultPrimitive.FaultAgain) .FallbackAsync(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. }; @@ -671,7 +671,7 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex [Fact] public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var policy = Policy @@ -679,13 +679,13 @@ public void Should_report_cancellation_and_not_execute_fallback_during_otherwise .OrResult(ResultPrimitive.FaultAgain) .FallbackAsync(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -702,7 +702,7 @@ public void Should_report_cancellation_and_not_execute_fallback_during_otherwise [Fact] public async Task Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var policy = Policy @@ -711,13 +711,13 @@ public async Task Should_handle_cancellation_and_execute_fallback_during_otherwi .Or() .FallbackAsync(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -733,7 +733,7 @@ public async Task Should_handle_cancellation_and_execute_fallback_during_otherwi [Fact] public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var policy = Policy @@ -741,13 +741,13 @@ public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non .OrResult(ResultPrimitive.FaultAgain) .FallbackAsync(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -763,7 +763,7 @@ public async Task Should_not_report_cancellation_and_not_execute_fallback_if_non [Fact] public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; var policy = Policy @@ -771,13 +771,13 @@ public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_acti .OrResult(ResultPrimitive.FaultAgain) .FallbackAsync(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -793,21 +793,21 @@ public async Task Should_report_unhandled_fault_and_not_execute_fallback_if_acti [Fact] public async Task Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - var fallbackActionExecuted = false; + bool fallbackActionExecuted = false; Func> fallbackAction = _ => { fallbackActionExecuted = true; return Task.FromResult(ResultPrimitive.Substitute); }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .FallbackAsync(fallbackAction); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false diff --git a/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs b/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs index 5f44d9d94c4..897684c7edb 100644 --- a/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs +++ b/src/Polly.Specs/Fallback/FallbackTResultSpecs.cs @@ -99,7 +99,7 @@ public void Should_throw_when_fallback_action_with_cancellation_is_null_with_onF [Fact] public void Should_throw_when_onFallback_delegate_is_null() { - var fallbackAction = () => ResultPrimitive.Substitute; + Func fallbackAction = () => ResultPrimitive.Substitute; Action> onFallback = null; Action policy = () => Policy @@ -159,10 +159,10 @@ public void Should_throw_when_onFallback_delegate_is_null_with_context_with_acti [Fact] public void Should_not_execute_fallback_when_executed_delegate_does_not_raise_fault() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction); @@ -174,10 +174,10 @@ public void Should_not_execute_fallback_when_executed_delegate_does_not_raise_fa [Fact] public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction); @@ -189,7 +189,7 @@ public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_ [Fact] public void Should_return_fallback_value_when_executed_delegate_raises_fault_handled_by_policy() { - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(ResultPrimitive.Substitute); @@ -199,10 +199,10 @@ public void Should_return_fallback_value_when_executed_delegate_raises_fault_han [Fact] public void Should_execute_fallback_when_executed_delegate_raises_fault_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction); @@ -214,10 +214,10 @@ public void Should_execute_fallback_when_executed_delegate_raises_fault_handled_ [Fact] public void Should_execute_fallback_when_executed_delegate_raises_one_of_results_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); @@ -230,10 +230,10 @@ public void Should_execute_fallback_when_executed_delegate_raises_one_of_results [Fact] public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_one_of_faults_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); @@ -246,10 +246,10 @@ public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_ [Fact] public void Should_not_execute_fallback_when_result_raised_does_not_match_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(_ => false) .Fallback(fallbackAction); @@ -261,10 +261,10 @@ public void Should_not_execute_fallback_when_result_raised_does_not_match_handli [Fact] public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_handled_by_any_of_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(r => r == ResultPrimitive.Fault) .OrResult(r => r == ResultPrimitive.FaultAgain) .Fallback(fallbackAction); @@ -277,10 +277,10 @@ public void Should_not_execute_fallback_when_executed_delegate_raises_fault_not_ [Fact] public void Should_execute_fallback_when_result_raised_matches_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(_ => true) .Fallback(fallbackAction); @@ -293,10 +293,10 @@ public void Should_execute_fallback_when_result_raised_matches_handling_predicat [Fact] public void Should_execute_fallback_when_result_raised_matches_one_of_handling_predicates() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(_ => true) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); @@ -309,14 +309,14 @@ public void Should_execute_fallback_when_result_raised_matches_one_of_handling_p [Fact] public void Should_not_handle_result_raised_by_fallback_delegate_even_if_is_result_handled_by_policy() { - var fallbackActionExecuted = false; - var fallbackAction = () => + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return new ResultClass(ResultPrimitive.Fault, "FromFallbackAction"); }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .Fallback(fallbackAction); @@ -333,17 +333,17 @@ public void Should_not_handle_result_raised_by_fallback_delegate_even_if_is_resu [Fact] public void Should_call_onFallback_passing_result_triggering_fallback() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return new ResultClass(ResultPrimitive.Substitute); }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return new ResultClass(ResultPrimitive.Substitute); }; ResultClass resultPassedToOnFallback = null; Action> onFallback = r => { resultPassedToOnFallback = r.Result; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); - var resultFromDelegate = new ResultClass(ResultPrimitive.Fault); + ResultClass resultFromDelegate = new ResultClass(ResultPrimitive.Fault); fallbackPolicy.Execute(() => resultFromDelegate); fallbackActionExecuted.Should().BeTrue(); @@ -354,12 +354,12 @@ public void Should_call_onFallback_passing_result_triggering_fallback() [Fact] public void Should_not_call_onFallback_when_executed_delegate_does_not_raise_fault() { - var fallbackAction = () => ResultPrimitive.Substitute; + Func fallbackAction = () => ResultPrimitive.Substitute; - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Action> onFallback = _ => { onFallbackExecuted = true; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -381,7 +381,7 @@ public void Should_call_onFallback_with_the_passed_context() Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -403,7 +403,7 @@ public void Should_call_onFallback_with_the_passed_context_when_execute_and_capt Action, Context> onFallback = (_, ctx) => { contextData = ctx; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -425,7 +425,7 @@ public void Should_call_onFallback_with_independent_context_for_independent_call Action, Context> onFallback = (dr, ctx) => { contextData[dr.Result] = ctx["key"]; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction, onFallback); @@ -447,12 +447,12 @@ public void Should_call_onFallback_with_independent_context_for_independent_call public void Context_should_be_empty_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var onFallbackExecuted = false; + bool onFallbackExecuted = false; Func fallbackAction = _ => ResultPrimitive.Substitute; Action, Context> onFallback = (_, ctx) => { onFallbackExecuted = true; capturedContext = ctx; }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction, onFallback); @@ -472,7 +472,7 @@ public void Should_call_fallbackAction_with_the_passed_context() Action, Context> onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -494,7 +494,7 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ Action, Context> onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -511,13 +511,13 @@ public void Should_call_fallbackAction_with_the_passed_context_when_execute_and_ public void Context_should_be_empty_at_fallbackAction_if_execute_not_called_with_any_context_data() { Context capturedContext = null; - var fallbackExecuted = false; + bool fallbackExecuted = false; Func fallbackAction = (ctx, _) => { fallbackExecuted = true; capturedContext = ctx; return ResultPrimitive.Substitute; }; Action, Context> onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction, onFallback); @@ -541,7 +541,7 @@ public void Should_call_fallbackAction_with_the_fault() Action, Context> onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -563,7 +563,7 @@ public void Should_call_fallbackAction_with_the_fault_when_execute_and_capture() Action, Context> onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -586,7 +586,7 @@ public void Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() Action, Context> onFallback = (_, _) => { }; - var fallbackPolicy = Policy + FallbackPolicy fallbackPolicy = Policy .HandleResult(ResultPrimitive.Fault) .Fallback(fallbackAction, onFallback); @@ -603,20 +603,20 @@ public void Should_not_call_fallbackAction_with_the_fault_if_fault_unhandled() [Fact] public void Should_execute_action_when_non_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -631,20 +631,20 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca [Fact] public void Should_execute_fallback_when_faulting_and_cancellationToken_not_cancelled() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -659,21 +659,21 @@ public void Should_execute_fallback_when_faulting_and_cancellationToken_not_canc [Fact] public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. }; @@ -692,21 +692,21 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex [Fact] public void Should_report_cancellation_and_not_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_does_not_handle_cancellations() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -723,21 +723,21 @@ public void Should_report_cancellation_and_not_execute_fallback_during_otherwise [Fact] public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non_faulting_action_execution_when_user_delegate_observes_cancellationToken_and_fallback_handles_cancellations() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Or() .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -753,20 +753,20 @@ public void Should_handle_cancellation_and_execute_fallback_during_otherwise_non [Fact] public void Should_not_report_cancellation_and_not_execute_fallback_if_non_faulting_action_execution_completes_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -782,20 +782,20 @@ public void Should_not_report_cancellation_and_not_execute_fallback_if_non_fault [Fact] public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_execution_raises_unhandled_fault_and_user_delegate_does_not_observe_the_set_cancellationToken() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -811,21 +811,21 @@ public void Should_report_unhandled_fault_and_not_execute_fallback_if_action_exe [Fact] public void Should_handle_handled_fault_and_execute_fallback_following_faulting_action_execution_when_user_delegate_does_not_observe_cancellationToken() { - var fallbackActionExecuted = false; - var fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; + bool fallbackActionExecuted = false; + Func fallbackAction = () => { fallbackActionExecuted = true; return ResultPrimitive.Substitute; }; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var policy = Policy + FallbackPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Fallback(fallbackAction); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false diff --git a/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs b/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs index e0197610f0d..269a57d27b5 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyExtensions.cs @@ -10,7 +10,7 @@ public static void RaiseException(this Policy policy, IDictionary contextData, Action configureException = null) where TException : Exception, new() { - var counter = 0; + int counter = 0; policy.Execute(_ => { diff --git a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs index 5e5078e48e1..a5b91ec81e4 100644 --- a/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/ContextualPolicyExtensionsAsync.cs @@ -11,7 +11,7 @@ public static class ContextualPolicyExtensionsAsync public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, IDictionary contextData, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() { - var counter = 0; + int counter = 0; return policy.ExecuteAsync((_, _) => { diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs index 9b1267263ab..464ca555a40 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AddBehaviourIfHandleEngine.cs @@ -16,7 +16,7 @@ internal static TResult Implementation( { try { - var result = action(context, cancellationToken); + TResult result = action(context, cancellationToken); if (shouldHandleResultPredicates.AnyMatch(result)) { @@ -27,7 +27,7 @@ internal static TResult Implementation( } catch (Exception ex) { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; diff --git a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs index 2178114eb3a..40801dec03f 100644 --- a/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs +++ b/src/Polly.Specs/Helpers/Custom/AddBehaviourIfHandle/AsyncAddBehaviourIfHandleEngine.cs @@ -18,7 +18,7 @@ internal static async Task ImplementationAsync( { try { - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); if (shouldHandleResultPredicates.AnyMatch(result)) { @@ -29,7 +29,7 @@ internal static async Task ImplementationAsync( } catch (Exception ex) { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; diff --git a/src/Polly.Specs/Helpers/PolicyExtensions.cs b/src/Polly.Specs/Helpers/PolicyExtensions.cs index 9c6feafd014..95798ba6746 100644 --- a/src/Polly.Specs/Helpers/PolicyExtensions.cs +++ b/src/Polly.Specs/Helpers/PolicyExtensions.cs @@ -14,7 +14,7 @@ public class ExceptionAndOrCancellationScenario public static void RaiseException(this Policy policy, TException instance) where TException : Exception { - var scenario = new ExceptionAndOrCancellationScenario + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { ActionObservesCancellation = false, AttemptDuringWhichToCancel = null, @@ -31,7 +31,7 @@ public static void RaiseException(this Policy policy, TException ins public static void RaiseException(this Policy policy, int numberOfTimesToRaiseException, Action configureException = null) where TException : Exception, new() { - var scenario = new ExceptionAndOrCancellationScenario + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { ActionObservesCancellation = false, AttemptDuringWhichToCancel = null, @@ -61,9 +61,9 @@ public static void RaiseException(this Policy policy, TException ins public static void RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception { - var counter = 0; + int counter = 0; - var cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; policy.Execute(ct => { @@ -91,9 +91,9 @@ public static void RaiseExceptionAndOrCancellation(this Policy polic public static TResult RaiseExceptionAndOrCancellation(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception { - var counter = 0; + int counter = 0; - var cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; return policy.Execute(ct => { diff --git a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs index 715ecbf6c40..d60d7fa9974 100644 --- a/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyExtensionsAsync.cs @@ -18,7 +18,7 @@ public class ExceptionAndOrCancellationScenario public static Task RaiseExceptionAsync(this AsyncPolicy policy, TException instance) where TException : Exception { - var scenario = new ExceptionAndOrCancellationScenario + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { ActionObservesCancellation = false, AttemptDuringWhichToCancel = null, @@ -35,7 +35,7 @@ public static Task RaiseExceptionAsync(this AsyncPolicy policy, TExc public static Task RaiseExceptionAsync(this AsyncPolicy policy, int numberOfTimesToRaiseException, Action configureException = null, CancellationToken cancellationToken = default) where TException : Exception, new() { - var scenario = new ExceptionAndOrCancellationScenario + ExceptionAndOrCancellationScenario scenario = new ExceptionAndOrCancellationScenario { ActionObservesCancellation = false, AttemptDuringWhichToCancel = null, @@ -65,9 +65,9 @@ public static Task RaiseExceptionAsync(this AsyncPolicy policy, TExc public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory) where TException : Exception { - var counter = 0; + int counter = 0; - var cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; return policy.ExecuteAsync(ct => { @@ -96,9 +96,9 @@ public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPo public static Task RaiseExceptionAndOrCancellationAsync(this AsyncPolicy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func exceptionFactory, TResult successResult) where TException : Exception { - var counter = 0; + int counter = 0; - var cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; return policy.ExecuteAsync(ct => { diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs index 83344379623..0491df6848e 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensions.cs @@ -47,7 +47,7 @@ public static TResult RaiseResultAndOrExceptionSequence(this Policy(this Policy< public static TResult RaiseResultSequenceAndOrCancellation(this Policy policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, IEnumerable resultsToRaise) { - var counter = 0; + int counter = 0; - var cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; using (var enumerator = resultsToRaise.GetEnumerator()) { diff --git a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs index 11bf1131d48..e9130ca9cf4 100644 --- a/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs +++ b/src/Polly.Specs/Helpers/PolicyTResultExtensionsAsync.cs @@ -60,7 +60,7 @@ public static async Task RaiseResultAndOrExceptionSequenceAsync RaiseResultSequenceAndOrCancellationAsync policy, Scenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, IEnumerable resultsToRaise) { - var counter = 0; + int counter = 0; - var cancellationToken = cancellationTokenSource.Token; + CancellationToken cancellationToken = cancellationTokenSource.Token; using (var enumerator = resultsToRaise.GetEnumerator()) { diff --git a/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs b/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs index 7a5bfaf17d0..64da0965670 100644 --- a/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs +++ b/src/Polly.Specs/Helpers/RateLimit/IRateLimiterExtensions.cs @@ -8,7 +8,7 @@ internal static class IRateLimiterExtensions { public static void ShouldPermitAnExecution(this IRateLimiter rateLimiter) { - var canExecute = rateLimiter.PermitExecution(); + (bool permitExecution, TimeSpan retryAfter) canExecute = rateLimiter.PermitExecution(); canExecute.permitExecution.Should().BeTrue(); canExecute.retryAfter.Should().Be(TimeSpan.Zero); @@ -16,7 +16,7 @@ public static void ShouldPermitAnExecution(this IRateLimiter rateLimiter) public static void ShouldPermitNExecutions(this IRateLimiter rateLimiter, long numberOfExecutions) { - for (var execution = 0; execution < numberOfExecutions; execution++) + for (int execution = 0; execution < numberOfExecutions; execution++) { rateLimiter.ShouldPermitAnExecution(); } @@ -24,7 +24,7 @@ public static void ShouldPermitNExecutions(this IRateLimiter rateLimiter, long n public static void ShouldNotPermitAnExecution(this IRateLimiter rateLimiter, TimeSpan? retryAfter = null) { - var canExecute = rateLimiter.PermitExecution(); + (bool permitExecution, TimeSpan retryAfter) canExecute = rateLimiter.PermitExecution(); canExecute.permitExecution.Should().BeFalse(); if (retryAfter == null) diff --git a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs index 639eab61e60..78b392604ea 100644 --- a/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs +++ b/src/Polly.Specs/IAsyncPolicyExtensionsSpecs.cs @@ -26,7 +26,7 @@ public async Task Converting_a_nongeneric_IAsyncPolicy_to_generic_should_return_ var breaker = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.Zero); IAsyncPolicy nonGenericPolicy = breaker; var genericPolicy = nonGenericPolicy.AsAsyncPolicy(); - var deleg = () => Task.FromResult(ResultPrimitive.Good); + Func> deleg = () => Task.FromResult(ResultPrimitive.Good); (await genericPolicy.ExecuteAsync(deleg)).Should().Be(ResultPrimitive.Good); breaker.Isolate(); diff --git a/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs b/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs index 37b7c1b7b04..88d2bad937f 100644 --- a/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs +++ b/src/Polly.Specs/ISyncPolicyExtensionsSpecs.cs @@ -22,10 +22,10 @@ public void Converting_a_nongeneric_ISyncPolicy_to_generic_should_return_an_ISyn { // Use a CircuitBreaker as a policy which we can easily manipulate to demonstrate that the executions are passing through the underlying non-generic policy. - var breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); ISyncPolicy nonGenericPolicy = breaker; var genericPolicy = nonGenericPolicy.AsPolicy(); - var deleg = () => ResultPrimitive.Good; + Func deleg = () => ResultPrimitive.Good; genericPolicy.Execute(deleg).Should().Be(ResultPrimitive.Good); breaker.Isolate(); diff --git a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs index a8c0535550a..ccb5f15d200 100644 --- a/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpAsyncSpecs.cs @@ -11,7 +11,7 @@ public class NoOpAsyncSpecs public void Should_execute_user_delegate() { var policy = Policy.NoOpAsync(); - var executed = false; + bool executed = false; policy.Awaiting(p => p.ExecuteAsync(() => { executed = true; return TaskHelper.EmptyTask; })) .Should().NotThrow(); @@ -24,9 +24,9 @@ public void Should_execute_user_delegate_without_adding_extra_cancellation_behav { var policy = Policy.NoOpAsync(); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); diff --git a/src/Polly.Specs/NoOp/NoOpSpecs.cs b/src/Polly.Specs/NoOp/NoOpSpecs.cs index ae844529b13..315f7f63851 100644 --- a/src/Polly.Specs/NoOp/NoOpSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpSpecs.cs @@ -10,8 +10,8 @@ public class NoOpSpecs [Fact] public void Should_execute_user_delegate() { - var policy = Policy.NoOp(); - var executed = false; + NoOpPolicy policy = Policy.NoOp(); + bool executed = false; policy.Invoking(x => x.Execute(() => { executed = true; })) .Should().NotThrow(); @@ -22,10 +22,10 @@ public void Should_execute_user_delegate() [Fact] public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() { - var policy = Policy.NoOp(); - var executed = false; + NoOpPolicy policy = Policy.NoOp(); + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); diff --git a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs index 3f600e4c925..50979d539d0 100644 --- a/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs @@ -29,7 +29,7 @@ public void Should_execute_user_delegate_without_adding_extra_cancellation_behav var policy = Policy.NoOpAsync(); int? result = null; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); diff --git a/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs b/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs index 2d3bf38cc51..d5da3e596b7 100644 --- a/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs +++ b/src/Polly.Specs/NoOp/NoOpTResultSpecs.cs @@ -10,7 +10,7 @@ public class NoOpTResultSpecs [Fact] public void Should_execute_user_delegate() { - var policy = Policy.NoOp(); + NoOpPolicy policy = Policy.NoOp(); int? result = null; policy.Invoking(x => result = x.Execute(() => 10)) @@ -23,10 +23,10 @@ public void Should_execute_user_delegate() [Fact] public void Should_execute_user_delegate_without_adding_extra_cancellation_behaviour() { - var policy = Policy.NoOp(); + NoOpPolicy policy = Policy.NoOp(); int? result = null; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); diff --git a/src/Polly.Specs/PolicyAsyncSpecs.cs b/src/Polly.Specs/PolicyAsyncSpecs.cs index a07b55bfeaa..4835ac5154e 100644 --- a/src/Polly.Specs/PolicyAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyAsyncSpecs.cs @@ -14,7 +14,7 @@ public class PolicyAsyncSpecs [Fact] public async Task Executing_the_policy_action_should_execute_the_specified_async_action() { - var executed = false; + bool executed = false; var policy = Policy .Handle() @@ -37,7 +37,7 @@ public async Task Executing_the_policy_function_should_execute_the_specified_asy .Handle() .RetryAsync((_, _) => { }); - var result = await policy.ExecuteAsync(() => Task.FromResult(2)); + int result = await policy.ExecuteAsync(() => Task.FromResult(2)); result.Should() .Be(2); @@ -213,8 +213,8 @@ public void Executing_the_policy_function_should_throw_when_context_is_null() [Fact] public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; var policy = Policy.NoOpAsync(); @@ -273,8 +273,8 @@ public void Execute_and_capturing_the_policy_function_should_throw_when_context_ [Fact] public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; var policy = Policy.NoOpAsync(); @@ -287,8 +287,8 @@ public async Task Execute_and_capturing_the_policy_function_should_pass_context_ [Fact] public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); var policy = Policy.NoOpAsync(); diff --git a/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs b/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs index bd2703adbb0..a02c3b347b0 100644 --- a/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyContextAndKeyAsyncSpecs.cs @@ -91,7 +91,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr { var policy = Policy.Handle().RetryAsync(); - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); @@ -105,7 +105,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr [Fact] public async Task Should_pass_PolicyKey_to_execution_context() { - var policyKey = Guid.NewGuid().ToString(); + string policyKey = Guid.NewGuid().ToString(); string policyKeySetOnExecutionContext = null; Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; @@ -119,13 +119,13 @@ public async Task Should_pass_PolicyKey_to_execution_context() [Fact] public async Task Should_pass_OperationKey_to_execution_context() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; string operationKeySetOnContext = null; Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; var retry = Policy.Handle().RetryAsync(1, onRetry); - var firstExecution = true; + bool firstExecution = true; await retry.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; @@ -142,13 +142,13 @@ await retry.ExecuteAsync(async _ => [Fact] public async Task Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() { - var policyKey = Guid.NewGuid().ToString(); + string policyKey = Guid.NewGuid().ToString(); string policyKeySetOnExecutionContext = null; Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; var retry = Policy.Handle().RetryAsync(1, onRetry).WithPolicyKey(policyKey); - var firstExecution = true; + bool firstExecution = true; await retry.ExecuteAsync(async () => { await TaskHelper.EmptyTask; @@ -166,13 +166,13 @@ await retry.ExecuteAsync(async () => [Fact] public async Task Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; string operationKeySetOnContext = null; Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; var retry = Policy.Handle().RetryAsync(1, onRetry); - var firstExecution = true; + bool firstExecution = true; await retry.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; @@ -274,7 +274,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr { var policy = Policy.HandleResult(0).RetryAsync(); - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); @@ -288,7 +288,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr [Fact] public async Task Should_pass_PolicyKey_to_execution_context() { - var policyKey = Guid.NewGuid().ToString(); + string policyKey = Guid.NewGuid().ToString(); string policyKeySetOnExecutionContext = null; Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; @@ -302,13 +302,13 @@ public async Task Should_pass_PolicyKey_to_execution_context() [Fact] public async Task Should_pass_OperationKey_to_execution_context() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; string operationKeySetOnContext = null; Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; var retry = Policy.HandleResult(ResultPrimitive.Fault).RetryAsync(1, onRetry); - var firstExecution = true; + bool firstExecution = true; await retry.ExecuteAsync(async _ => { await TaskHelper.EmptyTask; diff --git a/src/Polly.Specs/PolicyContextAndKeySpecs.cs b/src/Polly.Specs/PolicyContextAndKeySpecs.cs index 83a322e0b36..d35ef09807f 100644 --- a/src/Polly.Specs/PolicyContextAndKeySpecs.cs +++ b/src/Polly.Specs/PolicyContextAndKeySpecs.cs @@ -90,7 +90,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr { var policy = Policy.Handle().Retry(); - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); @@ -104,7 +104,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr [Fact] public void Should_pass_PolicyKey_to_execution_context() { - var policyKey = Guid.NewGuid().ToString(); + string policyKey = Guid.NewGuid().ToString(); string policyKeySetOnExecutionContext = null; Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; @@ -118,13 +118,13 @@ public void Should_pass_PolicyKey_to_execution_context() [Fact] public void Should_pass_OperationKey_to_execution_context() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; string operationKeySetOnContext = null; Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; var retry = Policy.Handle().Retry(1, onRetry); - var firstExecution = true; + bool firstExecution = true; retry.Execute(_ => { if (firstExecution) @@ -140,13 +140,13 @@ public void Should_pass_OperationKey_to_execution_context() [Fact] public void Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_non_generic_policy() { - var policyKey = Guid.NewGuid().ToString(); + string policyKey = Guid.NewGuid().ToString(); string policyKeySetOnExecutionContext = null; Action onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; var retry = Policy.Handle().Retry(1, onRetry).WithPolicyKey(policyKey); - var firstExecution = true; + bool firstExecution = true; retry.Execute(() => { if (firstExecution) @@ -163,13 +163,13 @@ public void Should_pass_PolicyKey_to_execution_context_in_generic_execution_on_n [Fact] public void Should_pass_OperationKey_to_execution_context_in_generic_execution_on_non_generic_policy() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; string operationKeySetOnContext = null; Action onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; var retry = Policy.Handle().Retry(1, onRetry); - var firstExecution = true; + bool firstExecution = true; retry.Execute(_ => { if (firstExecution) @@ -271,7 +271,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr { var policy = Policy.HandleResult(0).Retry(); - var retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; + string retrieveKeyWhenNotExplicitlyConfigured = policy.PolicyKey; Action configure = () => policy.WithPolicyKey(Guid.NewGuid().ToString()); @@ -285,7 +285,7 @@ public void Should_not_be_able_to_configure_the_policy_key_explicitly_after_retr [Fact] public void Should_pass_PolicyKey_to_execution_context() { - var policyKey = Guid.NewGuid().ToString(); + string policyKey = Guid.NewGuid().ToString(); string policyKeySetOnExecutionContext = null; Action, int, Context> onRetry = (_, _, context) => { policyKeySetOnExecutionContext = context.PolicyKey; }; @@ -299,13 +299,13 @@ public void Should_pass_PolicyKey_to_execution_context() [Fact] public void Should_pass_OperationKey_to_execution_context() { - var operationKey = "SomeKey"; + string operationKey = "SomeKey"; string operationKeySetOnContext = null; Action, int, Context> onRetry = (_, _, context) => { operationKeySetOnContext = context.OperationKey; }; var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1, onRetry); - var firstExecution = true; + bool firstExecution = true; retry.Execute(_ => { if (firstExecution) diff --git a/src/Polly.Specs/PolicySpecs.cs b/src/Polly.Specs/PolicySpecs.cs index df986ee1898..4554cbf9f5a 100644 --- a/src/Polly.Specs/PolicySpecs.cs +++ b/src/Polly.Specs/PolicySpecs.cs @@ -207,8 +207,8 @@ public void Executing_the_policy_function_should_throw_when_context_is_null() [Fact] public void Executing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; Policy policy = Policy.NoOp(); @@ -267,8 +267,8 @@ public void Execute_and_capturing_the_policy_function_should_throw_when_context_ [Fact] public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; Policy policy = Policy.NoOp(); @@ -281,8 +281,8 @@ public void Execute_and_capturing_the_policy_function_should_pass_context_to_exe [Fact] public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Policy policy = Policy.NoOp(); diff --git a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs index 3c39d814a16..94ac159d8c7 100644 --- a/src/Polly.Specs/PolicyTResultAsyncSpecs.cs +++ b/src/Polly.Specs/PolicyTResultAsyncSpecs.cs @@ -133,8 +133,8 @@ public void Execute_and_capturing_the_policy_function_should_throw_when_context_ [Fact] public async Task Executing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; var policy = Policy.NoOpAsync(); @@ -159,8 +159,8 @@ public void Execute_and_capturing_the_policy_function_should_throw_when_context_ [Fact] public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; var policy = Policy.NoOpAsync(); @@ -173,8 +173,8 @@ public async Task Execute_and_capturing_the_policy_function_should_pass_context_ [Fact] public async Task Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); var policy = Policy.NoOpAsync(); diff --git a/src/Polly.Specs/PolicyTResultSpecs.cs b/src/Polly.Specs/PolicyTResultSpecs.cs index ca28b3745a8..f7fb42d2741 100644 --- a/src/Polly.Specs/PolicyTResultSpecs.cs +++ b/src/Polly.Specs/PolicyTResultSpecs.cs @@ -119,8 +119,8 @@ public void Executing_the_policy_function_should_throw_when_context_is_null() [Fact] public void Executing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; Policy policy = Policy.NoOp(); @@ -156,8 +156,8 @@ public void Execute_and_capturing_the_policy_function_should_throw_when_context_ [Fact] public void Execute_and_capturing_the_policy_function_should_pass_context_to_executed_delegate() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Context capturedContext = null; Policy policy = Policy.NoOp(); @@ -170,8 +170,8 @@ public void Execute_and_capturing_the_policy_function_should_pass_context_to_exe [Fact] public void Execute_and_capturing_the_policy_function_should_pass_context_to_PolicyResult() { - var operationKey = "SomeKey"; - var executionContext = new Context(operationKey); + string operationKey = "SomeKey"; + Context executionContext = new Context(operationKey); Policy policy = Policy.NoOp(); diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs index 24f3f62fa0e..fb7d4fab8d9 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicySpecsBase.cs @@ -23,7 +23,7 @@ protected abstract IRateLimitPolicy GetPolicyViaSyntax( protected void ShouldPermitAnExecution(IRateLimitPolicy policy) { - (var permitExecution, var retryAfter) = TryExecuteThroughPolicy(policy); + (bool permitExecution, TimeSpan retryAfter) = TryExecuteThroughPolicy(policy); permitExecution.Should().BeTrue(); retryAfter.Should().Be(TimeSpan.Zero); @@ -31,7 +31,7 @@ protected void ShouldPermitAnExecution(IRateLimitPolicy policy) protected void ShouldPermitNExecutions(IRateLimitPolicy policy, long numberOfExecutions) { - for (var execution = 0; execution < numberOfExecutions; execution++) + for (int execution = 0; execution < numberOfExecutions; execution++) { ShouldPermitAnExecution(policy); } @@ -126,7 +126,7 @@ public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifie FixClock(); // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetPolicyViaSyntax(1, onePer); // Assert - first execution after initialising should always be permitted. @@ -148,7 +148,7 @@ public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_ex FixClock(); // Arrange. - var onePer = TimeSpan.FromSeconds(1); + TimeSpan onePer = TimeSpan.FromSeconds(1); var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); // Act - should be able to successfully take bucketCapacity items. @@ -170,7 +170,7 @@ public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_ FixClock(); // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -178,10 +178,10 @@ public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_ ShouldNotPermitAnExecution(rateLimiter); // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval - var experimentRepeats = bucketCapacity * 3; - var shortfallFromInterval = TimeSpan.FromTicks(1); - var notQuiteInterval = onePer - shortfallFromInterval; - for (var i = 0; i < experimentRepeats; i++) + int experimentRepeats = bucketCapacity * 3; + TimeSpan shortfallFromInterval = TimeSpan.FromTicks(1); + TimeSpan notQuiteInterval = onePer - shortfallFromInterval; + for (int i = 0; i < experimentRepeats; i++) { // Arrange - Advance clock not quite to the interval AdvanceClock(notQuiteInterval.Ticks); @@ -208,8 +208,8 @@ public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_aft FixClock(); // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -234,8 +234,8 @@ public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burs FixClock(); // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -260,8 +260,8 @@ public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burs FixClock(); // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetPolicyViaSyntax(1, onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -285,13 +285,13 @@ public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_o FixClock(); // Arrange - var onePer = TimeSpan.FromSeconds(1); + TimeSpan onePer = TimeSpan.FromSeconds(1); var rateLimiter = GetPolicyViaSyntax(1, onePer); // Arrange - parallel tasks all waiting on a manual reset event. ManualResetEventSlim gate = new(); Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; - for (var i = 0; i < parallelContention; i++) + for (int i = 0; i < parallelContention; i++) { tasks[i] = Task.Run(() => { diff --git a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs index 4b00d0a7669..3fc16eb21ef 100644 --- a/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs @@ -26,7 +26,7 @@ public void Ratelimiter_specifies_correct_wait_until_next_execution_by_custom_fa FixClock(); // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); Context contextPassedToRetryAfter = null; Func retryAfterFactory = (t, ctx) => { @@ -42,7 +42,7 @@ public void Ratelimiter_specifies_correct_wait_until_next_execution_by_custom_fa // (do nothing - time not advanced) // Act - try another execution. - var contextToPassIn = new Context(); + Context contextToPassIn = new Context(); var resultExpectedBlocked = TryExecuteThroughPolicy(rateLimiter, contextToPassIn, new ResultClassWithRetryAfter(ResultPrimitive.Good)); // Assert - should be blocked - time not advanced. diff --git a/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs b/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs index 9984e3ccab9..b5bcd58360a 100644 --- a/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs +++ b/src/Polly.Specs/RateLimit/RateLimitSpecsBase.cs @@ -16,9 +16,9 @@ public abstract class RateLimitSpecsBase /// The action containing fluent assertions, which must succeed within the timespan. protected void Within(TimeSpan timeSpan, Action actionContainingAssertions) { - var retryInterval = TimeSpan.FromSeconds(0.2); + TimeSpan retryInterval = TimeSpan.FromSeconds(0.2); - var watch = Stopwatch.StartNew(); + Stopwatch watch = Stopwatch.StartNew(); while (true) { try @@ -39,13 +39,13 @@ protected void Within(TimeSpan timeSpan, Action actionContainingAssertions) protected static void FixClock() { - var now = DateTimeOffset.UtcNow; + DateTimeOffset now = DateTimeOffset.UtcNow; SystemClock.DateTimeOffsetUtcNow = () => now; } protected static void AdvanceClock(TimeSpan advance) { - var now = SystemClock.DateTimeOffsetUtcNow(); + DateTimeOffset now = SystemClock.DateTimeOffsetUtcNow(); SystemClock.DateTimeOffsetUtcNow = () => now + advance; } diff --git a/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs b/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs index 222ad77a3a9..c296f0d5bf7 100644 --- a/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs +++ b/src/Polly.Specs/RateLimit/TokenBucketRateLimiterTestsBase.cs @@ -29,7 +29,7 @@ public void Given_bucket_capacity_one_and_time_not_advanced_ratelimiter_specifie FixClock(); // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetRateLimiter(onePer, 1); // Assert - first execution after initialising should always be permitted. @@ -51,7 +51,7 @@ public void Given_bucket_capacity_N_and_time_not_advanced_ratelimiter_permits_ex FixClock(); // Arrange. - var onePer = TimeSpan.FromSeconds(1); + TimeSpan onePer = TimeSpan.FromSeconds(1); var rateLimiter = GetRateLimiter(onePer, bucketCapacity); // Act - should be able to successfully take bucketCapacity items. @@ -73,7 +73,7 @@ public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_ FixClock(); // Arrange - var onePer = TimeSpan.FromSeconds(onePerSeconds); + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetRateLimiter(onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -81,10 +81,10 @@ public void Given_any_bucket_capacity_ratelimiter_permits_another_execution_per_ rateLimiter.ShouldNotPermitAnExecution(); // Act-Assert - repeatedly advance the clock towards the interval but not quite - then to the interval - var experimentRepeats = bucketCapacity * 3; - var shortfallFromInterval = TimeSpan.FromTicks(1); - var notQuiteInterval = onePer - shortfallFromInterval; - for (var i = 0; i < experimentRepeats; i++) + int experimentRepeats = bucketCapacity * 3; + TimeSpan shortfallFromInterval = TimeSpan.FromTicks(1); + TimeSpan notQuiteInterval = onePer - shortfallFromInterval; + for (int i = 0; i < experimentRepeats; i++) { // Arrange - Advance clock not quite to the interval AdvanceClock(notQuiteInterval.Ticks); @@ -111,8 +111,8 @@ public void Given_any_bucket_capacity_rate_limiter_permits_full_bucket_burst_aft FixClock(); // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetRateLimiter(onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -137,8 +137,8 @@ public void Given_any_bucket_capacity_rate_limiter_permits_half_full_bucket_burs FixClock(); // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetRateLimiter(onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -163,8 +163,8 @@ public void Given_any_bucket_capacity_rate_limiter_permits_only_full_bucket_burs FixClock(); // Arrange - var onePerSeconds = 1; - var onePer = TimeSpan.FromSeconds(onePerSeconds); + int onePerSeconds = 1; + TimeSpan onePer = TimeSpan.FromSeconds(onePerSeconds); var rateLimiter = GetRateLimiter(onePer, bucketCapacity); // Arrange - spend the initial bucket capacity. @@ -188,13 +188,13 @@ public void Given_immediate_parallel_contention_ratelimiter_still_only_permits_o FixClock(); // Arrange - var onePer = TimeSpan.FromSeconds(1); + TimeSpan onePer = TimeSpan.FromSeconds(1); var rateLimiter = GetRateLimiter(onePer, 1); // Arrange - parallel tasks all waiting on a manual reset event. - var gate = new ManualResetEventSlim(); + ManualResetEventSlim gate = new ManualResetEventSlim(); Task<(bool permitExecution, TimeSpan retryAfter)>[] tasks = new Task<(bool, TimeSpan)>[parallelContention]; - for (var i = 0; i < parallelContention; i++) + for (int i = 0; i < parallelContention; i++) { tasks[i] = Task.Run(() => { diff --git a/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs index feef9d4f0fb..04164ec0f7e 100644 --- a/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ConcurrentPolicyRegistrySpecs.cs @@ -20,14 +20,14 @@ public ConcurrentPolicyRegistrySpecs() public void Should_be_able_to_add_Policy_using_TryAdd() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var insert = _registry.TryAdd(key, policy); _registry.Count.Should().Be(1); insert.Should().Be(true); Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); var insert2 = _registry.TryAdd(key2, policy2); _registry.Count.Should().Be(2); @@ -38,14 +38,14 @@ public void Should_be_able_to_add_Policy_using_TryAdd() public void Should_be_able_to_add_PolicyTResult_using_TryAdd() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var insert = _registry.TryAdd(key, policy); _registry.Count.Should().Be(1); insert.Should().Be(true); Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); var insert2 = _registry.TryAdd(key2, policy2); _registry.Count.Should().Be(2); @@ -56,14 +56,14 @@ public void Should_be_able_to_add_PolicyTResult_using_TryAdd() public void Should_be_able_to_add_Policy_by_interface_using_TryAdd() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var insert = _registry.TryAdd(key, policy); _registry.Count.Should().Be(1); insert.Should().Be(true); ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); var insert2 = _registry.TryAdd(key2, policy2); _registry.Count.Should().Be(2); @@ -74,12 +74,12 @@ public void Should_be_able_to_add_Policy_by_interface_using_TryAdd() public void Should_be_able_to_remove_policy_with_TryRemove() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Count.Should().Be(1); - var removed = _registry.TryRemove(key, out IsPolicy removedPolicy); + bool removed = _registry.TryRemove(key, out IsPolicy removedPolicy); _registry.Count.Should().Be(0); removedPolicy.Should().BeSameAs(policy); removed.Should().BeTrue(); @@ -88,9 +88,9 @@ public void Should_be_able_to_remove_policy_with_TryRemove() [Fact] public void Should_report_false_from_TryRemove_if_no_Policy() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); - var removed = _registry.TryRemove(key, out IsPolicy removedPolicy); + bool removed = _registry.TryRemove(key, out IsPolicy removedPolicy); removed.Should().BeFalse(); } @@ -98,12 +98,12 @@ public void Should_report_false_from_TryRemove_if_no_Policy() public void Should_be_able_to_update_policy_with_TryUpdate() { Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, existingPolicy); Policy newPolicy = Policy.NoOp(); - var updated = _registry.TryUpdate(key, newPolicy, existingPolicy); + bool updated = _registry.TryUpdate(key, newPolicy, existingPolicy); updated.Should().BeTrue(); _registry[key].Should().BeSameAs(newPolicy); @@ -113,13 +113,13 @@ public void Should_be_able_to_update_policy_with_TryUpdate() public void Should_not_update_policy_with_TryUpdate_when_existingPolicy_mismatch() { Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, existingPolicy); - var someOtherPolicy = Policy.NoOp(); + NoOpPolicy someOtherPolicy = Policy.NoOp(); Policy newPolicy = Policy.NoOp(); - var updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); + bool updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); updated.Should().BeFalse(); _registry[key].Should().BeSameAs(existingPolicy); @@ -128,12 +128,12 @@ public void Should_not_update_policy_with_TryUpdate_when_existingPolicy_mismatch [Fact] public void Should_not_update_policy_with_TryUpdate_when_no_existing_value() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); - var someOtherPolicy = Policy.NoOp(); + NoOpPolicy someOtherPolicy = Policy.NoOp(); Policy newPolicy = Policy.NoOp(); - var updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); + bool updated = _registry.TryUpdate(key, newPolicy, someOtherPolicy); updated.Should().BeFalse(); _registry.ContainsKey(key).Should().BeFalse(); @@ -143,7 +143,7 @@ public void Should_not_update_policy_with_TryUpdate_when_no_existing_value() public void Should_add_with_GetOrAdd_with_value_when_no_existing_policy() { Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var returnedPolicy = _registry.GetOrAdd(key, newPolicy); @@ -154,7 +154,7 @@ public void Should_add_with_GetOrAdd_with_value_when_no_existing_policy() public void Should_add_with_GetOrAdd_with_factory_when_no_existing_policy() { Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var returnedPolicy = _registry.GetOrAdd(key, _ => newPolicy); @@ -165,7 +165,7 @@ public void Should_add_with_GetOrAdd_with_factory_when_no_existing_policy() public void Should_return_existing_with_GetOrAdd_with_value_when_existing_policy() { Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, existingPolicy); Policy newPolicy = Policy.NoOp(); @@ -179,7 +179,7 @@ public void Should_return_existing_with_GetOrAdd_with_value_when_existing_policy public void Should_return_existing_with_GetOrAdd_with_factory_when_existing_policy() { Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, existingPolicy); Policy newPolicy = Policy.NoOp(); @@ -193,7 +193,7 @@ public void Should_return_existing_with_GetOrAdd_with_factory_when_existing_poli public void Should_add_with_AddOrUpdate_with_value_when_no_existing_policy() { Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var returnedPolicy = _registry.AddOrUpdate( key, @@ -207,7 +207,7 @@ public void Should_add_with_AddOrUpdate_with_value_when_no_existing_policy() public void Should_add_with_AddOrUpdate_with_addfactory_when_no_existing_policy() { Policy newPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var returnedPolicy = _registry.AddOrUpdate( @@ -222,7 +222,7 @@ public void Should_add_with_AddOrUpdate_with_addfactory_when_no_existing_policy( public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addvalue_when_existing_policy() { Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, existingPolicy); const string policyKeyToDecorate = "SomePolicyKey"; @@ -243,7 +243,7 @@ public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addvalue_ public void Should_update_with_AddOrUpdate_with_updatefactory_ignoring_addfactory_when_existing_policy() { Policy existingPolicy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, existingPolicy); const string policyKeyToDecorate = "SomePolicyKey"; diff --git a/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs index 93731cc5174..8772f367f03 100644 --- a/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/PolicyRegistrySpecs.cs @@ -25,13 +25,13 @@ public PolicyRegistrySpecs() public void Should_be_able_to_add_Policy_using_Add() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Count.Should().Be(1); Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); _registry.Add(key2, policy2); _registry.Count.Should().Be(2); @@ -41,13 +41,13 @@ public void Should_be_able_to_add_Policy_using_Add() public void Should_be_able_to_add_PolicyTResult_using_Add() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Count.Should().Be(1); Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); _registry.Add(key2, policy2); _registry.Count.Should().Be(2); @@ -57,13 +57,13 @@ public void Should_be_able_to_add_PolicyTResult_using_Add() public void Should_be_able_to_add_Policy_by_interface_using_Add() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Count.Should().Be(1); ISyncPolicy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); _registry.Add>(key2, policy2); _registry.Count.Should().Be(2); @@ -73,13 +73,13 @@ public void Should_be_able_to_add_Policy_by_interface_using_Add() public void Should_be_able_to_add_Policy_using_Indexer() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry[key] = policy; _registry.Count.Should().Be(1); Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); _registry[key2] = policy2; _registry.Count.Should().Be(2); @@ -89,13 +89,13 @@ public void Should_be_able_to_add_Policy_using_Indexer() public void Should_be_able_to_add_PolicyTResult_using_Indexer() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry[key] = policy; _registry.Count.Should().Be(1); Policy policy2 = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); _registry[key2] = policy2; _registry.Count.Should().Be(2); @@ -105,7 +105,7 @@ public void Should_be_able_to_add_PolicyTResult_using_Indexer() public void Should_not_be_able_to_add_Policy_with_duplicate_key_using_Add() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Invoking(r => r.Add(key, policy)) .Should().NotThrow(); @@ -120,7 +120,7 @@ public void Should_not_be_able_to_add_Policy_with_duplicate_key_using_Add() public void Should_be_able_to_overwrite_existing_Policy_if_key_exists_when_inserting_using_Indexer() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); Policy policy_new = Policy.NoOp(); @@ -135,7 +135,7 @@ public void Should_be_able_to_overwrite_existing_Policy_if_key_exists_when_inser public void Should_be_able_to_overwrite_existing_PolicyTResult_if_key_exists_when_inserting_using_Indexer() { Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add>(key, policy); Policy policy_new = Policy.HandleResult(ResultPrimitive.Fault).Retry(); @@ -172,7 +172,7 @@ public void Should_throw_when_adding_Policy_using_Indexer_when_key_is_null() public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; _registry.Add(key, policy); @@ -184,7 +184,7 @@ public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() { Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; _registry.Add(key, policy); @@ -196,7 +196,7 @@ public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() { ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); ISyncPolicy outPolicy = null; _registry.Add(key, policy); @@ -208,7 +208,7 @@ public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() public void Should_be_able_to_retrieve_stored_Policy_using_Get() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Get(key).Should().BeSameAs(policy); @@ -218,7 +218,7 @@ public void Should_be_able_to_retrieve_stored_Policy_using_Get() public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() { Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Get>(key).Should().BeSameAs(policy); @@ -228,7 +228,7 @@ public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() { ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Get>(key).Should().BeSameAs(policy); @@ -238,7 +238,7 @@ public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry[key].Should().BeSameAs(policy); @@ -248,7 +248,7 @@ public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() { Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry[key].Should().BeSameAs(policy); @@ -258,7 +258,7 @@ public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() { ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry[key].Should().BeSameAs(policy); @@ -267,9 +267,9 @@ public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer( [Fact] public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; - var result = false; + bool result = false; _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) .Should().NotThrow(); @@ -280,9 +280,9 @@ public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryG [Fact] public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; - var result = false; + bool result = false; _registry.Invoking(r => result = r.TryGet(key, out outPolicy)) .Should().NotThrow(); @@ -293,9 +293,9 @@ public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryG [Fact] public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); ISyncPolicy outPolicy = null; - var result = false; + bool result = false; _registry.Invoking(r => result = r.TryGet>(key, out outPolicy)) .Should().NotThrow(); @@ -306,7 +306,7 @@ public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryG [Fact] public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy policy = null; _registry.Invoking(r => policy = r.Get(key)) .Should().Throw(); @@ -315,7 +315,7 @@ public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() [Fact] public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy policy = null; _registry.Invoking(r => policy = r.Get>(key)) .Should().Throw(); @@ -324,7 +324,7 @@ public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exi [Fact] public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); ISyncPolicy policy = null; _registry.Invoking(r => policy = r.Get>(key)) .Should().Throw(); @@ -333,7 +333,7 @@ public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_n [Fact] public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); IsPolicy outPolicy = null; _registry.Invoking(r => outPolicy = r[key]) .Should().Throw(); @@ -382,13 +382,13 @@ public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() public void Should_be_able_to_clear_registry() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Count.Should().Be(1); Policy policy2 = Policy.NoOp(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); _registry.Add(key2, policy2); _registry.Count.Should().Be(2); @@ -401,7 +401,7 @@ public void Should_be_able_to_clear_registry() public void Should_be_able_to_remove_policy() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.Count.Should().Be(1); @@ -425,12 +425,12 @@ public void Should_throw_when_removing_Policy_when_key_is_null() public void Should_be_able_to_check_if_key_exists() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); _registry.ContainsKey(key).Should().BeTrue(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); _registry.ContainsKey(key2).Should().BeFalse(); } diff --git a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs index 99650c09607..e05b5948c05 100644 --- a/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs +++ b/src/Polly.Specs/Registry/ReadOnlyPolicyRegistrySpecs.cs @@ -25,7 +25,7 @@ public ReadOnlyPolicyRegistrySpecs() public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; _registry.Add(key, policy); @@ -37,7 +37,7 @@ public void Should_be_able_to_retrieve_stored_Policy_using_TryGet() public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() { Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; _registry.Add(key, policy); @@ -49,7 +49,7 @@ public void Should_be_able_to_retrieve_stored_PolicyTResult_using_TryGet() public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() { ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); ISyncPolicy outPolicy = null; _registry.Add(key, policy); @@ -61,7 +61,7 @@ public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_TryGet() public void Should_be_able_to_retrieve_stored_Policy_using_Get() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); ReadOnlyRegistry.Get(key).Should().BeSameAs(policy); @@ -71,7 +71,7 @@ public void Should_be_able_to_retrieve_stored_Policy_using_Get() public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() { Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); @@ -81,7 +81,7 @@ public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Get() public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() { ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); ReadOnlyRegistry.Get>(key).Should().BeSameAs(policy); @@ -91,7 +91,7 @@ public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Get() public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); ReadOnlyRegistry[key].Should().BeSameAs(policy); @@ -101,7 +101,7 @@ public void Should_be_able_to_retrieve_stored_Policy_using_Indexer() public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() { Policy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); ReadOnlyRegistry[key].Should().BeSameAs(policy); @@ -111,7 +111,7 @@ public void Should_be_able_to_retrieve_stored_PolicyTResult_using_Indexer() public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer() { ISyncPolicy policy = Policy.HandleResult(ResultPrimitive.Fault).Retry(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); ReadOnlyRegistry[key].Should().BeSameAs(policy); @@ -120,9 +120,9 @@ public void Should_be_able_to_retrieve_stored_Policy_by_interface_using_Indexer( [Fact] public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGet() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; - var result = false; + bool result = false; ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) .Should().NotThrow(); @@ -133,9 +133,9 @@ public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryG [Fact] public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicyTResult() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy outPolicy = null; - var result = false; + bool result = false; ReadOnlyRegistry.Invoking(r => result = r.TryGet(key, out outPolicy)) .Should().NotThrow(); @@ -146,9 +146,9 @@ public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryG [Fact] public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetPolicy_by_interface() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); ISyncPolicy outPolicy = null; - var result = false; + bool result = false; ReadOnlyRegistry.Invoking(r => result = r.TryGet>(key, out outPolicy)) .Should().NotThrow(); @@ -159,7 +159,7 @@ public void Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryG [Fact] public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy policy = null; ReadOnlyRegistry.Invoking(r => policy = r.Get(key)) .Should().Throw(); @@ -168,7 +168,7 @@ public void Should_throw_while_retrieving_using_Get_when_key_does_not_exist() [Fact] public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exist() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); Policy policy = null; ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) .Should().Throw(); @@ -177,7 +177,7 @@ public void Should_throw_while_retrieving_using_GetTResult_when_key_does_not_exi [Fact] public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_not_exist() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); ISyncPolicy policy = null; ReadOnlyRegistry.Invoking(r => policy = r.Get>(key)) .Should().Throw(); @@ -186,7 +186,7 @@ public void Should_throw_while_retrieving_using_Get_by_interface_when_key_does_n [Fact] public void Should_throw_while_retrieving_when_key_does_not_exist_using_Indexer() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); IsPolicy outPolicy = null; ReadOnlyRegistry.Invoking(r => outPolicy = r[key]) .Should().Throw(); @@ -236,12 +236,12 @@ public void Should_throw_when_retrieving_using_Indexer_when_key_is_null() public void Should_be_able_to_check_if_key_exists() { Policy policy = Policy.NoOp(); - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); _registry.Add(key, policy); ReadOnlyRegistry.ContainsKey(key).Should().BeTrue(); - var key2 = Guid.NewGuid().ToString(); + string key2 = Guid.NewGuid().ToString(); ReadOnlyRegistry.ContainsKey(key2).Should().BeFalse(); } @@ -274,7 +274,7 @@ public void Calling_The_GetEnumerator_Method_Returning_A_IEnumerator_Of_KeyValue [Fact] public void Policies_Should_Be_Added_To_The_Registry_When_Using_Collection_Initializer_Syntax() { - var key = Guid.NewGuid().ToString(); + string key = Guid.NewGuid().ToString(); var policy = Policy.NoOp(); var testRegistry = new PolicyRegistry diff --git a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs index a9dfe62f9e8..da10076de10 100644 --- a/src/Polly.Specs/Retry/RetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryAsyncSpecs.cs @@ -345,7 +345,7 @@ public void Should_create_new_context_for_each_call_to_execute_and_capture() [Fact] public void Should_not_call_onretry_when_retry_count_is_zero() { - var retryInvoked = false; + bool retryInvoked = false; Action onRetry = (_, _) => { retryInvoked = true; }; @@ -369,10 +369,10 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' of the retry policy would/could occur before onRetry execution had completed. // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; var policy = Policy .Handle() @@ -402,13 +402,13 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -427,13 +427,13 @@ public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_can .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, @@ -452,13 +452,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -480,13 +480,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -507,13 +507,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -534,13 +534,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -561,13 +561,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -588,13 +588,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -615,13 +615,13 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -642,13 +642,13 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -664,8 +664,8 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ [Fact] public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() @@ -674,10 +674,10 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. @@ -698,15 +698,15 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -728,15 +728,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs index 90cfe107c45..a96eb9e3c95 100644 --- a/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/RetryForeverAsyncSpecs.cs @@ -220,10 +220,10 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; var policy = Policy .Handle() @@ -253,13 +253,13 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -278,13 +278,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -306,13 +306,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -333,13 +333,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -360,13 +360,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -387,13 +387,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -414,13 +414,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -437,8 +437,8 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ [Fact] public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() @@ -448,10 +448,10 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. @@ -472,15 +472,15 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -502,15 +502,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .RetryForeverAsync(); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Retry/RetrySpecs.cs b/src/Polly.Specs/Retry/RetrySpecs.cs index 4665da82b0d..705fd6b18a1 100644 --- a/src/Polly.Specs/Retry/RetrySpecs.cs +++ b/src/Polly.Specs/Retry/RetrySpecs.cs @@ -467,7 +467,7 @@ public void Should_create_new_state_for_each_call_to_policy() [Fact] public void Should_not_call_onretry_when_retry_count_is_zero_without_context() { - var retryInvoked = false; + bool retryInvoked = false; Action onRetry = (_, _) => { retryInvoked = true; }; @@ -484,7 +484,7 @@ public void Should_not_call_onretry_when_retry_count_is_zero_without_context() [Fact] public void Should_not_call_onretry_when_retry_count_is_zero_with_context() { - var retryInvoked = false; + bool retryInvoked = false; Action onRetry = (_, _, _) => { retryInvoked = true; }; @@ -508,13 +508,13 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -533,13 +533,13 @@ public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_can .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, @@ -558,13 +558,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -586,13 +586,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -613,13 +613,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -640,13 +640,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -667,13 +667,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -694,13 +694,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -721,13 +721,13 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -748,13 +748,13 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -770,8 +770,8 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ [Fact] public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() @@ -780,10 +780,10 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. @@ -804,15 +804,15 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -833,15 +833,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs b/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs index dcbd5e273fa..35edcb851b7 100644 --- a/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs +++ b/src/Polly.Specs/Retry/RetryTResultMixedResultExceptionSpecs.cs @@ -13,7 +13,7 @@ public void Should_handle_exception_when_TResult_policy_handling_exceptions_only Policy policy = Policy .Handle().Retry(1); - var result = policy.RaiseResultAndOrExceptionSequence(new DivideByZeroException(), ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(new DivideByZeroException(), ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -35,7 +35,7 @@ public void Should_handle_both_exception_and_specified_result_if_raised_same_num .Or() .Retry(2); - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -47,7 +47,7 @@ public void Should_handle_both_exception_and_specified_result_if_raised_same_num .OrResult(ResultPrimitive.Fault) .Retry(2); - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -61,7 +61,7 @@ public void Should_handle_both_exceptions_and_specified_results_if_raised_same_n .OrResult(ResultPrimitive.FaultAgain) .Retry(4); - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -75,7 +75,7 @@ public void Should_handle_both_exceptions_and_specified_results_if_raised_same_n .Or() .Retry(4); - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -89,7 +89,7 @@ public void Should_return_handled_result_when_handled_result_returned_next_after .Or() .Retry(3); - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultAgain); } @@ -117,7 +117,7 @@ public void Should_return_handled_result_when_handled_result_returned_next_after .OrResult(ResultPrimitive.FaultAgain) .Retry(3); - var result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultAndOrExceptionSequence(ResultPrimitive.Fault, new DivideByZeroException(), new ArgumentException(), ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultAgain); } @@ -143,7 +143,7 @@ public void Should_return_unhandled_result_if_not_one_of_results_or_exceptions_s .Or() .Retry(2); - var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain); result.Should().Be(ResultPrimitive.FaultAgain); } @@ -167,7 +167,7 @@ public void Should_handle_both_exceptions_and_specified_results_with_predicates( .OrResult(r => r.ResultCode == ResultPrimitive.Fault) .Retry(2); - var result = policy.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message","key"), new ResultClass(ResultPrimitive.Good)); + ResultClass result = policy.RaiseResultAndOrExceptionSequence(new ResultClass(ResultPrimitive.Fault), new ArgumentException("message","key"), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.Good); } @@ -191,7 +191,7 @@ public void Should_return_unhandled_result_if_result_predicate_not_matched() .OrResult(r => r.ResultCode == ResultPrimitive.Fault) .Retry(2); - var result = policy.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"), new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + ResultClass result = policy.RaiseResultAndOrExceptionSequence(new ArgumentException("message", "key"), new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); } } diff --git a/src/Polly.Specs/Retry/RetryTResultSpecs.cs b/src/Polly.Specs/Retry/RetryTResultSpecs.cs index 3712bdb9207..4cd5bfd5abb 100644 --- a/src/Polly.Specs/Retry/RetryTResultSpecs.cs +++ b/src/Polly.Specs/Retry/RetryTResultSpecs.cs @@ -68,92 +68,92 @@ public void Should_throw_when_onretry_action_with_context_is_null() [Fact] public void Should_not_return_handled_result_when_handled_result_raised_same_number_of_times_as_retry_count() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } [Fact] public void Should_not_return_handled_result_when_one_of_the_handled_results_raised_same_number_of_times_as_retry_count() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } [Fact] public void Should_not_return_handled_result_when_handled_result_raised_less_number_of_times_than_retry_count() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } [Fact] public void Should_not_return_handled_result_when_all_of_the_handled_results_raised_less_number_of_times_than_retry_count() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } [Fact] public void Should_return_handled_result_when_handled_result_raised_more_times_then_retry_count() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. } [Fact] public void Should_return_handled_result_when_one_of_the_handled_results_is_raised_more_times_then_retry_count() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Retry(3); - var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultAgain); } [Fact] public void Should_return_result_when_result_is_not_the_specified_handled_result() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(); - var result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultAgain); } [Fact] public void Should_return_result_when_result_is_not_one_of_the_specified_handled_results() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .OrResult(ResultPrimitive.FaultAgain) .Retry(); - var result = policy.RaiseResultSequence(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); + ResultPrimitive result = policy.RaiseResultSequence(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultYetAgain); } @@ -164,7 +164,7 @@ public void Should_return_result_when_specified_result_predicate_is_not_satisfie .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); } @@ -176,7 +176,7 @@ public void Should_return_result_when_none_of_the_specified_result_predicates_ar .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); } @@ -187,7 +187,7 @@ public void Should_not_return_handled_result_when_specified_result_predicate_is_ .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.Good); } @@ -199,7 +199,7 @@ public void Should_not_return_handled_result_when_one_of_the_specified_result_pr .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) .Retry(); - var result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + ResultClass result = policy.RaiseResultSequence(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.Good); } @@ -209,7 +209,7 @@ public void Should_call_onretry_on_each_retry_with_the_current_retry_count() var expectedRetryCounts = new[] { 1, 2, 3 }; var retryCounts = new List(); - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3, (_, retryCount) => retryCounts.Add(retryCount)); @@ -244,7 +244,7 @@ public void Should_not_call_onretry_when_no_retries_are_performed() { var retryCounts = new List(); - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry((_, retryCount) => retryCounts.Add(retryCount)); @@ -259,7 +259,7 @@ public void Should_call_onretry_with_the_passed_context() { IDictionary contextData = null; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry((_, _, context) => contextData = context); @@ -279,11 +279,11 @@ public void Should_call_onretry_with_the_passed_context_when_execute_and_capture { IDictionary contextData = null; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry((_, _, context) => contextData = context); - var result = policy.RaiseResultSequenceOnExecuteAndCapture( + PolicyResult result = policy.RaiseResultSequenceOnExecuteAndCapture( new { key1 = "value1", key2 = "value2" }.AsDictionary(), ResultPrimitive.Fault, ResultPrimitive.Good ); @@ -308,7 +308,7 @@ public void Context_should_be_empty_if_execute_not_called_with_any_context_data( { Context capturedContext = null; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry((_, _, context) => capturedContext = context); @@ -323,7 +323,7 @@ public void Should_create_new_context_for_each_call_to_execute() { string contextValue = null; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry((_, _, context) => contextValue = context["key"].ToString()); @@ -347,7 +347,7 @@ public void Should_create_new_context_for_each_call_to_execute_and_capture() { string contextValue = null; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry((_, _, context) => contextValue = context["key"].ToString()); @@ -369,7 +369,7 @@ public void Should_create_new_context_for_each_call_to_execute_and_capture() [Fact] public void Should_create_new_state_for_each_call_to_policy() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(1); @@ -382,11 +382,11 @@ public void Should_create_new_state_for_each_call_to_policy() [Fact] public void Should_not_call_onretry_when_retry_count_is_zero_without_context() { - var retryInvoked = false; + bool retryInvoked = false; Action, int> onRetry = (_, _) => { retryInvoked = true; }; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(0, onRetry); @@ -398,11 +398,11 @@ public void Should_not_call_onretry_when_retry_count_is_zero_without_context() [Fact] public void Should_not_call_onretry_when_retry_count_is_zero_with_context() { - var retryInvoked = false; + bool retryInvoked = false; Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(0, onRetry); @@ -422,13 +422,13 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -442,17 +442,17 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca [Fact] public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_cancelled() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -470,17 +470,17 @@ public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_can [Fact] public void Should_not_execute_action_when_cancellationToken_cancelled_before_execute() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. }; @@ -501,17 +501,17 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex [Fact] public void Should_report_cancellation_during_otherwise_non_faulting_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -531,17 +531,17 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec [Fact] public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -561,17 +561,17 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ [Fact] public void Should_report_cancellation_during_faulting_initial_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -591,17 +591,17 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ [Fact] public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_observes_cancellationToken() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 2, ActionObservesCancellation = true @@ -621,17 +621,17 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ [Fact] public void Should_report_cancellation_during_faulting_retried_action_execution_and_cancel_further_retries_when_user_delegate_does_not_observe_cancellationToken() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 2, ActionObservesCancellation = false @@ -651,17 +651,17 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ [Fact] public void Should_report_cancellation_during_faulting_last_retry_execution_when_user_delegate_does_observe_cancellationToken() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1 + 3, ActionObservesCancellation = true @@ -682,17 +682,17 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when [Fact] public void Should_report_faulting_from_faulting_last_retry_execution_when_user_delegate_does_not_observe_cancellation_raised_during_last_retry() { - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1 + 3, ActionObservesCancellation = false @@ -712,20 +712,20 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ [Fact] public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var policy = Policy + RetryPolicy policy = Policy .HandleResult(ResultPrimitive.Fault) .Retry(3, (_, _) => { cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. ActionObservesCancellation = false diff --git a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs index e9d0b35d41d..2f4b940a48f 100644 --- a/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs +++ b/src/Polly.Specs/Retry/RetryTResultSpecsAsync.cs @@ -73,7 +73,7 @@ public async Task Should_not_return_handled_result_when_handled_result_raised_sa .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -85,7 +85,7 @@ public async Task Should_not_return_handled_result_when_one_of_the_handled_resul .OrResult(ResultPrimitive.FaultAgain) .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -96,7 +96,7 @@ public async Task Should_not_return_handled_result_when_handled_result_raised_le .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -108,7 +108,7 @@ public async Task Should_not_return_handled_result_when_all_of_the_handled_resul .OrResult(ResultPrimitive.FaultAgain) .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Good); } @@ -119,7 +119,7 @@ public async Task Should_return_handled_result_when_handled_result_raised_more_t .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Fault, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.Fault); // It should give up retrying after 3 retries and return the last failure, so should return Fault, not Good. } @@ -131,7 +131,7 @@ public async Task Should_return_handled_result_when_one_of_the_handled_results_i .OrResult(ResultPrimitive.FaultAgain) .RetryAsync(3); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultAgain); } @@ -142,7 +142,7 @@ public async Task Should_return_result_when_result_is_not_the_specified_handled_ .HandleResult(ResultPrimitive.Fault) .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultAgain); } @@ -154,7 +154,7 @@ public async Task Should_return_result_when_result_is_not_one_of_the_specified_h .OrResult(ResultPrimitive.FaultAgain) .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); + ResultPrimitive result = await policy.RaiseResultSequenceAsync(ResultPrimitive.FaultYetAgain, ResultPrimitive.Good); result.Should().Be(ResultPrimitive.FaultYetAgain); } @@ -165,7 +165,7 @@ public async Task Should_return_result_when_specified_result_predicate_is_not_sa .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.FaultAgain); } @@ -177,7 +177,7 @@ public async Task Should_return_result_when_none_of_the_specified_result_predica .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultYetAgain), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.FaultYetAgain); } @@ -188,7 +188,7 @@ public async Task Should_not_return_handled_result_when_specified_result_predica .HandleResult(r => r.ResultCode == ResultPrimitive.Fault) .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.Fault), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.Good); } @@ -200,7 +200,7 @@ public async Task Should_not_return_handled_result_when_one_of_the_specified_res .OrResult(r => r.ResultCode == ResultPrimitive.FaultAgain) .RetryAsync(); - var result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); + ResultClass result = await policy.RaiseResultSequenceAsync(new ResultClass(ResultPrimitive.FaultAgain), new ResultClass(ResultPrimitive.Good)); result.ResultCode.Should().Be(ResultPrimitive.Good); } @@ -287,7 +287,7 @@ public async Task Should_call_onretry_with_the_passed_context_when_execute_and_c .HandleResult(ResultPrimitive.Fault) .RetryAsync((_, _, context) => contextData = context); - var result = await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( + PolicyResult result = await policy.RaiseResultSequenceOnExecuteAndCaptureAsync( new { key1 = "value1", key2 = "value2" }.AsDictionary(), ResultPrimitive.Fault, ResultPrimitive.Good ); @@ -386,7 +386,7 @@ public async Task Should_create_new_state_for_each_call_to_policy() [Fact] public async Task Should_not_call_onretry_when_retry_count_is_zero_without_context() { - var retryInvoked = false; + bool retryInvoked = false; Action, int> onRetry = (_, _) => { retryInvoked = true; }; @@ -402,7 +402,7 @@ public async Task Should_not_call_onretry_when_retry_count_is_zero_without_conte [Fact] public async Task Should_not_call_onretry_when_retry_count_is_zero_with_context() { - var retryInvoked = false; + bool retryInvoked = false; Action, int, Context> onRetry = (_, _, _) => { retryInvoked = true; }; @@ -427,10 +427,10 @@ public async Task Should_wait_asynchronously_for_async_onretry_delegate() // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -460,13 +460,13 @@ public async Task Should_execute_all_tries_when_faulting_and_cancellationToken_n .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, }; @@ -488,13 +488,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. }; @@ -519,13 +519,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -549,13 +549,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = true @@ -579,13 +579,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1, ActionObservesCancellation = false @@ -609,13 +609,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 2, ActionObservesCancellation = true @@ -639,13 +639,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 2, ActionObservesCancellation = false @@ -669,13 +669,13 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1 + 3, ActionObservesCancellation = true @@ -700,13 +700,13 @@ public async Task Should_report_faulting_from_faulting_last_retry_execution_when .HandleResult(ResultPrimitive.Fault) .RetryAsync(3); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = 1 + 3, ActionObservesCancellation = false @@ -726,8 +726,8 @@ public async Task Should_report_faulting_from_faulting_last_retry_execution_when [Fact] public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .HandleResult(ResultPrimitive.Fault) @@ -736,10 +736,10 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. ActionObservesCancellation = false diff --git a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs index 8d29426202b..97d17ae1103 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryAsyncSpecs.cs @@ -567,7 +567,7 @@ public async Task Should_be_able_to_pass_handled_exception_to_sleepdurationprovi { object capturedExceptionInstance = null; - var exceptionInstance = new DivideByZeroException(); + DivideByZeroException exceptionInstance = new DivideByZeroException(); var policy = Policy .Handle() @@ -587,7 +587,7 @@ public async Task Should_be_able_to_pass_handled_exception_to_sleepdurationprovi [Fact] public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {new DivideByZeroException(), 2.Seconds()}, {new ArgumentNullException(), 4.Seconds()}, @@ -622,7 +622,7 @@ public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepD var expectedRetryDuration = 1.Seconds(); TimeSpan? actualRetryDuration = null; - var defaultRetryAfter = 30.Seconds(); + TimeSpan defaultRetryAfter = 30.Seconds(); var policy = Policy .Handle() @@ -631,7 +631,7 @@ public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepD onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. ); - var failedOnce = false; + bool failedOnce = false; await policy.ExecuteAsync(async (context, _) => { await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. @@ -653,7 +653,7 @@ await policy.ExecuteAsync(async (context, _) => [Fact] public void Should_not_call_onretry_when_retry_count_is_zero() { - var retryInvoked = false; + bool retryInvoked = false; Action onRetry = (_, _) => { retryInvoked = true; }; @@ -675,10 +675,10 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; var policy = Policy .Handle() @@ -710,13 +710,13 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -735,13 +735,13 @@ public void Should_execute_all_tries_when_faulting_and_cancellationToken_not_can .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, @@ -760,13 +760,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -788,13 +788,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -815,13 +815,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -842,13 +842,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -869,13 +869,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -896,13 +896,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -923,13 +923,13 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -950,13 +950,13 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -972,25 +972,25 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ [Fact] public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; SystemClock.SleepAsync = Task.Delay; - var shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; var policy = Policy .Handle() .WaitAndRetryAsync(new[] { retryDelay }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); watch.Start(); - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 1, AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. @@ -1013,8 +1013,8 @@ public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandr [Fact] public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() @@ -1024,10 +1024,10 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. @@ -1048,15 +1048,15 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null @@ -1078,15 +1078,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .WaitAndRetryAsync(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs index 0d60d20272e..3a312efe95d 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverAsyncSpecs.cs @@ -309,7 +309,7 @@ public async Task Should_calculate_retry_timespans_from_current_retry_attempt_an [Fact] public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {new DivideByZeroException(), 2.Seconds()}, {new ArgumentNullException(), 4.Seconds()}, @@ -345,7 +345,7 @@ public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepD var expectedRetryDuration = 1.Seconds(); TimeSpan? actualRetryDuration = null; - var defaultRetryAfter = 30.Seconds(); + TimeSpan defaultRetryAfter = 30.Seconds(); var policy = Policy .Handle() @@ -354,7 +354,7 @@ public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepD onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. ); - var failedOnce = false; + bool failedOnce = false; await policy.ExecuteAsync(async (context, _) => { await TaskHelper.EmptyTask; // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. @@ -381,10 +381,10 @@ public void Should_wait_asynchronously_for_async_onretry_delegate() // If Polly were to declare only an Action<...> delegate for onRetry - but users declared async () => { } onRetry delegates - the compiler would happily assign them to the Action<...>, but the next 'try' would/could occur before onRetry execution had completed. // This test ensures the relevant retry policy does have a Func<..., Task> form for onRetry, and that it is awaited before the next try commences. - var shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.2); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var executeDelegateInvocations = 0; - var executeDelegateInvocationsWhenOnRetryExits = 0; + int executeDelegateInvocations = 0; + int executeDelegateInvocationsWhenOnRetryExits = 0; var policy = Policy .Handle() @@ -418,12 +418,12 @@ public void Should_execute_action_when_non_faulting_and_cancellationToken_not_ca .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -444,13 +444,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -474,13 +474,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -503,13 +503,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -532,13 +532,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -561,13 +561,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -590,13 +590,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -615,8 +615,8 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance { Func provider = _ => TimeSpan.Zero; - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() @@ -626,10 +626,10 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. @@ -652,14 +652,14 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null, @@ -682,15 +682,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .WaitAndRetryForeverAsync(provider); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new Scenario + Scenario scenario = new Scenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs index 7ba4bd39c57..b7b795eaafe 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverSpecs.cs @@ -306,7 +306,7 @@ public void Should_calculate_retry_timespans_from_current_retry_attempt_and_time [Fact] public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {new DivideByZeroException(), 2.Seconds()}, {new ArgumentNullException(), 4.Seconds()}, @@ -335,7 +335,7 @@ public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDuratio var expectedRetryDuration = 1.Seconds(); TimeSpan? actualRetryDuration = null; - var defaultRetryAfter = 30.Seconds(); + TimeSpan defaultRetryAfter = 30.Seconds(); var policy = Policy .Handle() @@ -344,7 +344,7 @@ public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDuratio onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. ); - var failedOnce = false; + bool failedOnce = false; policy.Execute(context => { // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs index 9bf5d284b13..2d0075a4fad 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs @@ -21,7 +21,7 @@ public WaitAndRetryForeverTResultAsyncSpecs() [Fact] public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {ResultPrimitive.Fault, 2.Seconds()}, {ResultPrimitive.FaultAgain, 4.Seconds()}, diff --git a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs index ce394135e76..068b22cc1c5 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryForeverTResultSpecs.cs @@ -20,7 +20,7 @@ public WaitAndRetryForeverTResultSpecs() [Fact] public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {ResultPrimitive.Fault, 2.Seconds()}, {ResultPrimitive.FaultAgain, 4.Seconds()}, diff --git a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs index 922d60e83ab..c95b002007e 100644 --- a/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetrySpecs.cs @@ -712,7 +712,7 @@ public void Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() { object capturedExceptionInstance = null; - var exceptionInstance = new DivideByZeroException(); + DivideByZeroException exceptionInstance = new DivideByZeroException(); var policy = Policy .Handle() @@ -735,7 +735,7 @@ public void Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() [Fact] public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {new DivideByZeroException(), 2.Seconds()}, {new ArgumentNullException(), 4.Seconds()}, @@ -764,7 +764,7 @@ public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDuratio var expectedRetryDuration = 1.Seconds(); TimeSpan? actualRetryDuration = null; - var defaultRetryAfter = 30.Seconds(); + TimeSpan defaultRetryAfter = 30.Seconds(); var policy = Policy .Handle() @@ -773,7 +773,7 @@ public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDuratio onRetry: (_, timeSpan, _) => actualRetryDuration = timeSpan // Capture the actual sleep duration that was used, for test verification purposes. ); - var failedOnce = false; + bool failedOnce = false; policy.Execute(context => { // Run some remote call; maybe it returns a RetryAfter header, which we can pass back to the sleepDurationProvider, via the context. @@ -793,7 +793,7 @@ public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDuratio [Fact] public void Should_not_call_onretry_when_retry_count_is_zero_without_context() { - var retryInvoked = false; + bool retryInvoked = false; Action onRetry = (_, _) => { retryInvoked = true; }; @@ -810,7 +810,7 @@ public void Should_not_call_onretry_when_retry_count_is_zero_without_context() [Fact] public void Should_not_call_onretry_when_retry_count_is_zero_with_context() { - var retryInvoked = false; + bool retryInvoked = false; Action onRetry = (_, _, _) => { retryInvoked = true; }; @@ -827,7 +827,7 @@ public void Should_not_call_onretry_when_retry_count_is_zero_with_context() [Fact] public void Should_not_call_onretry_when_retry_count_is_zero_with_attempts_with_context() { - var retryInvoked = false; + bool retryInvoked = false; Action onRetry = (_, _, _, _) => { retryInvoked = true; }; @@ -850,13 +850,13 @@ public void Should_not_execute_action_when_cancellationToken_cancelled_before_ex .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation token cancelled manually below - before any scenario execution. @@ -878,13 +878,13 @@ public void Should_report_cancellation_during_otherwise_non_faulting_action_exec .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, @@ -905,13 +905,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -932,13 +932,13 @@ public void Should_report_cancellation_during_faulting_initial_action_execution_ .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1, @@ -959,13 +959,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -986,13 +986,13 @@ public void Should_report_cancellation_during_faulting_retried_action_execution_ .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 2, @@ -1013,13 +1013,13 @@ public void Should_report_cancellation_during_faulting_last_retry_execution_when .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -1040,13 +1040,13 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = 1 + 3, @@ -1062,25 +1062,25 @@ public void Should_report_faulting_from_faulting_last_retry_execution_when_user_ [Fact] public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandretry() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; SystemClock.Sleep = (timeSpan, ct) => Task.Delay(timeSpan, ct).Wait(ct); - var shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; + TimeSpan shimTimeSpan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan retryDelay = shimTimeSpan + shimTimeSpan + shimTimeSpan; var policy = Policy .Handle() .WaitAndRetry(new[] { retryDelay }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); watch.Start(); - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 1, AttemptDuringWhichToCancel = null, // Cancellation invoked after delay - see below. @@ -1103,8 +1103,8 @@ public void Should_honour_cancellation_immediately_during_wait_phase_of_waitandr [Fact] public void Should_report_cancellation_after_faulting_action_execution_and_cancel_further_retries_if_onRetry_invokes_cancellation() { - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; var policy = Policy .Handle() @@ -1114,10 +1114,10 @@ public void Should_report_cancellation_after_faulting_action_execution_and_cance cancellationTokenSource.Cancel(); }); - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 1 + 3, AttemptDuringWhichToCancel = null, // Cancellation during onRetry instead - see above. @@ -1138,15 +1138,15 @@ public void Should_execute_func_returning_value_when_cancellationToken_not_cance .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = null @@ -1167,15 +1167,15 @@ public void Should_honour_and_report_cancellation_during_func_execution() .Handle() .WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() }); - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + CancellationToken cancellationToken = cancellationTokenSource.Token; - var attemptsInvoked = 0; + int attemptsInvoked = 0; Action onExecute = () => attemptsInvoked++; bool? result = null; - var scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario + PolicyExtensions.ExceptionAndOrCancellationScenario scenario = new PolicyExtensions.ExceptionAndOrCancellationScenario { NumberOfTimesToRaiseException = 0, AttemptDuringWhichToCancel = 1, diff --git a/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs index dc9169512a1..44f90ab8d9d 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryTResultAsyncSpecs.cs @@ -21,7 +21,7 @@ public WaitAndRetryTResultAsyncSpecs() [Fact] public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {ResultPrimitive.Fault, 2.Seconds()}, {ResultPrimitive.FaultAgain, 4.Seconds()}, diff --git a/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs b/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs index f1c7f13bcb7..f5346a462f8 100644 --- a/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs +++ b/src/Polly.Specs/Retry/WaitAndRetryTResultSpecs.cs @@ -20,7 +20,7 @@ public WaitAndRetryTResultSpecs() [Fact] public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() { - var expectedRetryWaits = new Dictionary(){ + Dictionary expectedRetryWaits = new Dictionary(){ {ResultPrimitive.Fault, 2.Seconds()}, {ResultPrimitive.FaultAgain, 4.Seconds()}, diff --git a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs index fc9edef40c8..a1923d69f7e 100644 --- a/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutAsyncSpecs.cs @@ -201,7 +201,7 @@ public void Should_be_able_to_configure_with_timeout_func() [Fact] public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { - var timeout = TimeSpan.FromMilliseconds(50); + TimeSpan timeout = TimeSpan.FromMilliseconds(50); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); @@ -217,9 +217,9 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe { var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - var result = ResultPrimitive.Undefined; + ResultPrimitive result = ResultPrimitive.Undefined; - var act = async () => + Func act = async () => { result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); }; @@ -231,12 +231,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe [Fact] public void Should_throw_timeout_after_correct_duration__pessimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Awaiting(p => p.ExecuteAsync(async () => @@ -266,7 +266,7 @@ public void Should_rethrow_exception_from_inside_delegate__pessimistic() [Fact] public void Should_throw_when_timeout_is_less_than_execution_duration__optimistic() { - var timeout = TimeSpan.FromMilliseconds(50); + TimeSpan timeout = TimeSpan.FromMilliseconds(50); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); var userCancellationToken = CancellationToken.None; @@ -286,7 +286,7 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op var result = ResultPrimitive.Undefined; var userCancellationToken = CancellationToken.None; - var act = async () => { + Func act = async () => { result = await policy.ExecuteAsync(async ct => { await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(500), ct); @@ -301,13 +301,13 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op [Fact] public void Should_throw_timeout_after_correct_duration__optimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); var userCancellationToken = CancellationToken.None; - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Awaiting(p => p.ExecuteAsync(async ct => @@ -336,10 +336,10 @@ public void Should_rethrow_exception_from_inside_delegate__optimistic() [Fact] public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { - var timeout = 5; + int timeout = 5; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Awaiting(p => p.ExecuteAsync(async _ => { @@ -357,9 +357,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -381,10 +381,10 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled [Fact] public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - var timeout = 10; + int timeout = 10; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Awaiting(p => p.ExecuteAsync( ct => { @@ -400,9 +400,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -459,7 +459,7 @@ public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeo [Fact] public void Should_call_ontimeout_with_configured_timeout__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -483,8 +483,8 @@ public void Should_call_ontimeout_with_configured_timeout__pessimistic() [Fact] public void Should_call_ontimeout_with_passed_context__pessimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Func onTimeoutAsync = (ctx, _, _) => @@ -493,7 +493,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); policy.Awaiting(p => p.ExecuteAsync(async _ => @@ -514,7 +514,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -552,7 +552,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; policy.Awaiting(p => p.ExecuteAsync(async _ => { @@ -573,7 +573,7 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimist return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); policy.Awaiting(p => p.ExecuteAsync(async () => @@ -601,8 +601,8 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo return TaskHelper.EmptyTask; }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); policy.Awaiting(p => p.ExecuteAsync(async () => @@ -621,7 +621,7 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo [Fact] public void Should_call_ontimeout_with_timing_out_exception__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, _, exception) => @@ -650,7 +650,7 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() [Fact] public void Should_call_ontimeout_with_configured_timeout__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -675,8 +675,8 @@ public void Should_call_ontimeout_with_configured_timeout__optimistic() [Fact] public void Should_call_ontimeout_with_passed_context__optimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Func onTimeoutAsync = (ctx, _, _) => @@ -685,7 +685,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; @@ -707,7 +707,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -748,7 +748,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var userCancellationToken = CancellationToken.None; // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; @@ -773,7 +773,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; @@ -790,7 +790,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o [Fact] public void Should_call_ontimeout_with_timing_out_exception__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, _, exception) => diff --git a/src/Polly.Specs/Timeout/TimeoutSpecs.cs b/src/Polly.Specs/Timeout/TimeoutSpecs.cs index 1ceffb35417..c8bc4aa1342 100644 --- a/src/Polly.Specs/Timeout/TimeoutSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutSpecs.cs @@ -233,7 +233,7 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe var result = ResultPrimitive.Undefined; var userCancellationToken = CancellationToken.None; - var act = () => { + Action act = () => { result = policy.Execute(ct => { SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); @@ -248,12 +248,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe [Fact] public void Should_throw_timeout_after_correct_duration__pessimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(10), CancellationToken.None))) @@ -278,7 +278,7 @@ public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic var msg = "Aggregate Exception thrown from the delegate"; // Check to see if nested aggregate exceptions are unwrapped correctly - var exception = new AggregateException(msg, new NotImplementedException()); + AggregateException exception = new AggregateException(msg, new NotImplementedException()); policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); })) .Should().Throw() @@ -295,7 +295,7 @@ public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_ins Exception innerException1 = new NotImplementedException(); Exception innerException2 = new DivideByZeroException(); - var aggregateException = new AggregateException(msg, innerException1, innerException2); + AggregateException aggregateException = new AggregateException(msg, innerException1, innerException2); Action action = () => throw aggregateException; // Whether executing the delegate directly, or through the policy, exception behavior should be the same. @@ -315,10 +315,10 @@ public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_ex Exception innerException1 = new NotImplementedException(); Exception innerException2 = new DivideByZeroException(); - var action = () => + Action action = () => { - var task1 = Task.Run(() => throw innerException1); - var task2 = Task.Run(() => throw innerException2); + Task task1 = Task.Run(() => throw innerException1); + Task task2 = Task.Run(() => throw innerException2); Task.WhenAll(task1, task2).Wait(); }; @@ -337,7 +337,7 @@ public void Should_rethrow_aggregate_exception_with_another_example_cause_of_mul Exception innerException1 = new NotImplementedException(); Exception innerException2 = new DivideByZeroException(); - var action = () => + Action action = () => { Action action1 = () => throw innerException1; Action action2 = () => throw innerException2; @@ -373,7 +373,7 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op var result = ResultPrimitive.Undefined; var userCancellationToken = CancellationToken.None; - var act = () => { + Action act = () => { result = policy.Execute(ct => { SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); @@ -388,13 +388,13 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op [Fact] public void Should_throw_timeout_after_correct_duration__optimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.Timeout(timeout); var userCancellationToken = CancellationToken.None; - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(10), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. @@ -420,10 +420,10 @@ public void Should_rethrow_exception_from_inside_delegate__optimistic() [Fact] public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { - var timeout = 5; + int timeout = 5; var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Invoking(p => p.Execute( _ => @@ -443,9 +443,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -463,10 +463,10 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled [Fact] public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - var timeout = 10; + int timeout = 10; var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Invoking(p => p.Execute( ct => { userTokenSource.Cancel(); ct.ThrowIfCancellationRequested(); } // Simulate cancel in the middle of execution @@ -480,9 +480,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -535,7 +535,7 @@ public void Should_not_mask_user_exception_if_user_exception_overlaps_with_timeo [Fact] public void Should_call_ontimeout_with_configured_timeout__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -551,13 +551,13 @@ public void Should_call_ontimeout_with_configured_timeout__pessimistic() [Fact] public void Should_call_ontimeout_with_passed_context__pessimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), contextPassedToExecute)) @@ -574,7 +574,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -600,7 +600,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") {["timeout"] = TimeSpan.FromMilliseconds(25* programaticallyControlledDelay) }; + Context context = new Context("SomeOperationKey") {["timeout"] = TimeSpan.FromMilliseconds(25* programaticallyControlledDelay) }; policy.Invoking(p => p.Execute(_ => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), context)) .Should().Throw(); @@ -614,7 +614,7 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimist Task taskPassedToOnTimeout = null; Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) @@ -638,8 +638,8 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); policy.Invoking(p => p.Execute(() => @@ -658,7 +658,7 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c [Fact] public void Should_call_ontimeout_with_timing_out_exception__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; @@ -679,7 +679,7 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() [Fact] public void Should_call_ontimeout_with_configured_timeout__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -696,13 +696,13 @@ public void Should_call_ontimeout_with_configured_timeout__optimistic() [Fact] public void Should_call_ontimeout_with_passed_context__optimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); var userCancellationToken = CancellationToken.None; @@ -720,7 +720,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -749,7 +749,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var userCancellationToken = CancellationToken.None; // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; @@ -766,7 +766,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o Task taskPassedToOnTimeout = null; Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); var userCancellationToken = CancellationToken.None; @@ -779,7 +779,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o [Fact] public void Should_call_ontimeout_with_timing_out_exception__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; diff --git a/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs b/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs index adadf33bf60..d86d7b7c7a3 100644 --- a/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs +++ b/src/Polly.Specs/Timeout/TimeoutSpecsBase.cs @@ -36,7 +36,7 @@ protected TimeoutSpecsBase() _trackedTokenSource = tokenSource; - var newCancelAt = _offsetUtcNow.Add(timespan); + DateTimeOffset newCancelAt = _offsetUtcNow.Add(timespan); _cancelAt = newCancelAt < _cancelAt ? newCancelAt : _cancelAt; SystemClock.Sleep(TimeSpan.Zero, CancellationToken.None); // Invoke our custom definition of sleep, to check for immediate cancellation. @@ -56,7 +56,7 @@ protected TimeoutSpecsBase() else { // Tracking something to cancel - does this sleep hit time to cancel? - var timeToCancellation = _cancelAt - _offsetUtcNow; + TimeSpan timeToCancellation = _cancelAt - _offsetUtcNow; if (sleepTimespan >= timeToCancellation) { // Cancel! (And advance time only to the instant of cancellation) @@ -64,7 +64,7 @@ protected TimeoutSpecsBase() _utcNow += timeToCancellation; // (and stop tracking it after cancelling; it can't be cancelled twice, so there is no need, and the owner may dispose it) - var copySource = _trackedTokenSource; + CancellationTokenSource copySource = _trackedTokenSource; _trackedTokenSource = null; copySource.Cancel(); copySource.Token.ThrowIfCancellationRequested(); diff --git a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs index 09c3cd7376e..a178dbbfd1d 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -202,7 +202,7 @@ public void Should_be_able_to_configure_with_timeout_func() [Fact] public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { - var timeout = TimeSpan.FromMilliseconds(50); + TimeSpan timeout = TimeSpan.FromMilliseconds(50); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); @@ -218,7 +218,7 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe { var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Pessimistic); - var result = ResultPrimitive.Undefined; + ResultPrimitive result = ResultPrimitive.Undefined; Func act = async () => result = await policy.ExecuteAsync(() => Task.FromResult(ResultPrimitive.Good)); @@ -229,12 +229,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe [Fact] public void Should_throw_timeout_after_correct_duration__pessimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Awaiting(p => p.ExecuteAsync(async () => @@ -278,7 +278,7 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op { var policy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1), TimeoutStrategy.Optimistic); - var result = ResultPrimitive.Undefined; + ResultPrimitive result = ResultPrimitive.Undefined; var userCancellationToken = CancellationToken.None; Func act = async () => result = await policy.ExecuteAsync(_ => Task.FromResult(ResultPrimitive.Good), userCancellationToken); @@ -290,13 +290,13 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op [Fact] public void Should_throw_timeout_after_correct_duration__optimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); var userCancellationToken = CancellationToken.None; - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Awaiting(p => p.ExecuteAsync(async ct => @@ -325,10 +325,10 @@ public void Should_rethrow_exception_from_inside_delegate__optimistic() [Fact] public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { - var timeout = 5; + int timeout = 5; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Awaiting(p => p.ExecuteAsync(async _ => { @@ -347,9 +347,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Pessimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -372,9 +372,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled [Fact] public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - var timeout = 10; + int timeout = 10; var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Awaiting(p => p.ExecuteAsync( ct => { @@ -390,9 +390,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.TimeoutAsync(10, TimeoutStrategy.Optimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -415,7 +415,7 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled [Fact] public void Should_call_ontimeout_with_configured_timeout__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -439,8 +439,8 @@ public void Should_call_ontimeout_with_configured_timeout__pessimistic() [Fact] public void Should_call_ontimeout_with_passed_context__pessimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Func onTimeoutAsync = (ctx, _, _) => @@ -449,7 +449,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); policy.Awaiting(p => p.ExecuteAsync(async _ => @@ -470,7 +470,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -509,7 +509,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; policy.Awaiting(p => p.ExecuteAsync(async _ => { @@ -531,7 +531,7 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimist return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); policy.Awaiting(p => p.ExecuteAsync(async () => @@ -560,8 +560,8 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo return TaskHelper.EmptyTask; }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); policy.Awaiting(p => p.ExecuteAsync(async () => @@ -580,7 +580,7 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo [Fact] public void Should_call_ontimeout_with_timing_out_exception__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, _, exception) => @@ -609,7 +609,7 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() [Fact] public void Should_call_ontimeout_with_configured_timeout__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -634,8 +634,8 @@ public void Should_call_ontimeout_with_configured_timeout__optimistic() [Fact] public void Should_call_ontimeout_with_passed_context__optimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Func onTimeoutAsync = (ctx, _, _) => @@ -644,7 +644,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; @@ -667,7 +667,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Func onTimeoutAsync = (_, span, _) => @@ -708,7 +708,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var userCancellationToken = CancellationToken.None; // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; @@ -733,7 +733,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o return TaskHelper.EmptyTask; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; @@ -750,7 +750,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o [Fact] public void Should_call_ontimeout_with_timing_out_exception__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Func onTimeoutAsync = (_, _, _, exception) => diff --git a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs index 3f77613bd18..1cbf43fe6ad 100644 --- a/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs +++ b/src/Polly.Specs/Timeout/TimeoutTResultSpecs.cs @@ -201,7 +201,7 @@ public void Should_be_able_to_configure_with_timeout_func() [Fact] public void Should_throw_when_timeout_is_less_than_execution_duration__pessimistic() { - var timeout = TimeSpan.FromMilliseconds(50); + TimeSpan timeout = TimeSpan.FromMilliseconds(50); var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); @@ -220,7 +220,7 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe var result = ResultPrimitive.Undefined; var userCancellationToken = CancellationToken.None; - var act = () => { + Action act = () => { result = policy.Execute(ct => { SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); @@ -235,12 +235,12 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__pe [Fact] public void Should_throw_timeout_after_correct_duration__pessimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Invoking(p => p.Execute(() => @@ -269,7 +269,7 @@ public void Should_rethrow_aggregate_exception_from_inside_delegate__pessimistic var msg = "Aggregate Exception thrown from the delegate"; // Check to see if nested aggregate exceptions are unwrapped correctly - var exception = new AggregateException(msg, new NotImplementedException()); + AggregateException exception = new AggregateException(msg, new NotImplementedException()); policy.Invoking(p => p.Execute(() => { Helper_ThrowException(exception); return ResultPrimitive.WhateverButTooLate; })) .Should().Throw() @@ -286,8 +286,8 @@ public void Should_rethrow_aggregate_exception_with_multiple_exceptions_from_ins Exception innerException1 = new NotImplementedException(); Exception innerException2 = new DivideByZeroException(); - var aggregateException = new AggregateException(msg, innerException1, innerException2); - var func = () => { Helper_ThrowException(aggregateException); return ResultPrimitive.WhateverButTooLate; }; + AggregateException aggregateException = new AggregateException(msg, innerException1, innerException2); + Func func = () => { Helper_ThrowException(aggregateException); return ResultPrimitive.WhateverButTooLate; }; // Whether executing the delegate directly, or through the policy, exception behavior should be the same. func.Should().Throw() @@ -306,10 +306,10 @@ public void Should_rethrow_aggregate_exception_with_example_cause_of_multiple_ex Exception innerException1 = new NotImplementedException(); Exception innerException2 = new DivideByZeroException(); - var func = () => + Func func = () => { - var task1 = Task.Run(() => throw innerException1); - var task2 = Task.Run(() => throw innerException2); + Task task1 = Task.Run(() => throw innerException1); + Task task2 = Task.Run(() => throw innerException2); Task.WhenAll(task1, task2).Wait(); return ResultPrimitive.WhateverButTooLate; }; @@ -329,9 +329,9 @@ public void Should_rethrow_aggregate_exception_with_another_example_cause_of_mul Exception innerException1 = new NotImplementedException(); Exception innerException2 = new DivideByZeroException(); - var func = () => + Func func = () => { - var action1 = () => { throw innerException1; }; + Action action1 = () => { throw innerException1; }; Action action2 = () => throw innerException2; Parallel.Invoke(action1, action2); return ResultPrimitive.WhateverButTooLate; @@ -370,7 +370,7 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op var result = ResultPrimitive.Undefined; var userCancellationToken = CancellationToken.None; - var act = () => { + Action act = () => { result = policy.Execute(ct => { SystemClock.Sleep(TimeSpan.FromMilliseconds(500), ct); @@ -385,13 +385,13 @@ public void Should_not_throw_when_timeout_is_greater_than_execution_duration__op [Fact] public void Should_throw_timeout_after_correct_duration__optimistic() { - var watch = new Stopwatch(); + Stopwatch watch = new Stopwatch(); - var timeout = TimeSpan.FromSeconds(1); + TimeSpan timeout = TimeSpan.FromSeconds(1); var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); var userCancellationToken = CancellationToken.None; - var tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. + TimeSpan tolerance = TimeSpan.FromSeconds(3); // Consider increasing tolerance, if test fails transiently in different test/build environments. watch.Start(); policy.Invoking(p => p.Execute(ct => @@ -420,10 +420,10 @@ public void Should_rethrow_exception_from_inside_delegate__optimistic() [Fact] public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token_before_timeout__pessimistic() { - var timeout = 5; + int timeout = 5; var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Invoking(p => p.Execute( _ => { @@ -442,9 +442,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.Timeout(10, TimeoutStrategy.Pessimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -466,9 +466,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled [Fact] public void Should_be_able_to_cancel_with_user_cancellation_token_before_timeout__optimistic() { - var timeout = 10; + int timeout = 10; var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic); - using (var userTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Invoking(p => p.Execute( ct => { @@ -484,9 +484,9 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled { var policy = Policy.Timeout(10, TimeoutStrategy.Optimistic); - var executed = false; + bool executed = false; - using (var cts = new CancellationTokenSource()) + using (CancellationTokenSource cts = new CancellationTokenSource()) { cts.Cancel(); @@ -508,7 +508,7 @@ public void Should_not_execute_user_delegate_if_user_cancellationToken_cancelled [Fact] public void Should_call_ontimeout_with_configured_timeout__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -528,13 +528,13 @@ public void Should_call_ontimeout_with_configured_timeout__pessimistic() [Fact] public void Should_call_ontimeout_with_passed_context__pessimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); policy.Invoking(p => p.Execute(_ => @@ -556,7 +556,7 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -587,7 +587,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; policy.Invoking(p => p.Execute(_ => { @@ -605,7 +605,7 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action__pessimist Task taskPassedToOnTimeout = null; Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); policy.Invoking(p => p.Execute(() => @@ -633,8 +633,8 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); }; - var shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - var thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); policy.Invoking(p => p.Execute(() => @@ -653,7 +653,7 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c [Fact] public void Should_call_ontimeout_with_timing_out_exception__pessimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; @@ -679,7 +679,7 @@ public void Should_call_ontimeout_with_timing_out_exception__pessimistic() [Fact] public void Should_call_ontimeout_with_configured_timeout__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -700,13 +700,13 @@ public void Should_call_ontimeout_with_configured_timeout__optimistic() [Fact] public void Should_call_ontimeout_with_passed_context__optimistic() { - var operationKey = "SomeKey"; - var contextPassedToExecute = new Context(operationKey); + string operationKey = "SomeKey"; + Context contextPassedToExecute = new Context(operationKey); Context contextPassedToOnTimeout = null; Action onTimeout = (ctx, _, _) => { contextPassedToOnTimeout = ctx; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); var userCancellationToken = CancellationToken.None; @@ -728,7 +728,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { - var timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (_, span, _) => { timeoutPassedToOnTimeout = span; }; @@ -760,7 +760,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu var userCancellationToken = CancellationToken.None; // Supply a programatically-controlled timeout, via the execution context. - var context = new Context("SomeOperationKey") + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; @@ -781,7 +781,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o Task taskPassedToOnTimeout = null; Action onTimeout = (_, _, task) => { taskPassedToOnTimeout = task; }; - var timeout = TimeSpan.FromMilliseconds(250); + TimeSpan timeout = TimeSpan.FromMilliseconds(250); var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); var userCancellationToken = CancellationToken.None; @@ -798,7 +798,7 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o [Fact] public void Should_call_ontimeout_with_timing_out_exception__optimistic() { - var timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Action onTimeout = (_, _, _, exception) => { exceptionPassedToOnTimeout = exception; }; diff --git a/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs b/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs index 75bd867ad0c..6a1f703d300 100644 --- a/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs +++ b/src/Polly.Specs/Wrap/IPolicyWrapExtensionSpecs.cs @@ -16,13 +16,13 @@ public class IPolicyWrapExtensionSpecs [Fact] public void Should_pass_all_nested_policies_from_PolicyWrap_in_same_order_they_were_added() { - var policy0 = Policy.NoOp(); - var policy1 = Policy.NoOp(); - var policy2 = Policy.NoOp(); + NoOpPolicy policy0 = Policy.NoOp(); + NoOpPolicy policy1 = Policy.NoOp(); + NoOpPolicy policy2 = Policy.NoOp(); - var policyWrap = Policy.Wrap(policy0, policy1, policy2); + PolicyWrap policyWrap = Policy.Wrap(policy0, policy1, policy2); - var policies = policyWrap.GetPolicies().ToList(); + List policies = policyWrap.GetPolicies().ToList(); policies.Count.Should().Be(3); policies[0].Should().Be(policy0); policies[1].Should().Be(policy1); @@ -34,7 +34,7 @@ public void Should_return_sequence_from_GetPolicies() { Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = Policy.Wrap(policyA, policyB); + PolicyWrap wrap = Policy.Wrap(policyA, policyB); wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB }, options => options @@ -49,7 +49,7 @@ public void Threepolicies_by_static_sequence_should_return_correct_sequence_from Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); Policy policyC = Policy.NoOp(); - var wrap = Policy.Wrap(policyA, policyB, policyC); + PolicyWrap wrap = Policy.Wrap(policyA, policyB, policyC); wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, options => options @@ -64,7 +64,7 @@ public void Threepolicies_lefttree_should_return_correct_sequence_from_GetPolici Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB).Wrap(policyC); + PolicyWrap wrap = policyA.Wrap(policyB).Wrap(policyC); wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, options => options @@ -79,7 +79,7 @@ public void Threepolicies_righttree_should_return_correct_sequence_from_GetPolic Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyB, policyC }, options => options @@ -94,7 +94,7 @@ public void GetPoliciesTPolicy_should_return_single_policy_of_type_TPolicy() Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyB }, options => options @@ -109,7 +109,7 @@ public void GetPoliciesTPolicy_should_return_empty_enumerable_if_no_policy_of_ty Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicies().Should().BeEmpty(); } @@ -120,7 +120,7 @@ public void GetPoliciesTPolicy_should_return_multiple_policies_of_type_TPolicy() Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicies().Should().BeEquivalentTo(new[] { policyA, policyC }, options => options @@ -132,13 +132,13 @@ public void GetPoliciesTPolicy_should_return_multiple_policies_of_type_TPolicy() [Fact] public void GetPoliciesTPolicy_should_return_policies_of_type_TPolicy_matching_predicate() { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); policyA.Isolate(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicies(p => p.CircuitState == CircuitState.Closed).Should().BeEquivalentTo(new[] { policyC }, options => options @@ -150,11 +150,11 @@ public void GetPoliciesTPolicy_should_return_policies_of_type_TPolicy_matching_p [Fact] public void GetPoliciesTPolicy_should_return_empty_enumerable_if_none_match_predicate() { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicies(p => p.CircuitState == CircuitState.Open).Should().BeEmpty(); } @@ -165,7 +165,7 @@ public void GetPoliciesTPolicy_with_predicate_should_return_multiple_policies_of Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicies(_ => true).Should().BeEquivalentTo(new[] { policyA, policyC }, options => options @@ -179,7 +179,7 @@ public void GetPoliciesTPolicy_with_predicate_should_throw_if_predicate_is_null( { Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); + PolicyWrap wrap = policyA.Wrap(policyB); Action configure = () => wrap.GetPolicies(null); @@ -192,7 +192,7 @@ public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy() Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicy().Should().BeSameAs(policyB); } @@ -203,7 +203,7 @@ public void GetPolicyTPolicy_should_return_null_if_no_TPolicy() Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicy().Should().BeNull(); } @@ -214,7 +214,7 @@ public void GetPolicyTPolicy_should_throw_if_multiple_TPolicy() Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.Invoking(p => p.GetPolicy()).Should().Throw(); } @@ -222,13 +222,13 @@ public void GetPolicyTPolicy_should_throw_if_multiple_TPolicy() [Fact] public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy_matching_predicate() { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); policyA.Isolate(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicy(p => p.CircuitState == CircuitState.Closed).Should().BeSameAs(policyC); } @@ -236,11 +236,11 @@ public void GetPolicyTPolicy_should_return_single_policy_of_type_TPolicy_matchin [Fact] public void GetPolicyTPolicy_should_return_null_if_none_match_predicate() { - var policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyA = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); Policy policyB = Policy.Handle().Retry(); - var policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); + CircuitBreakerPolicy policyC = Policy.Handle().CircuitBreaker(1, TimeSpan.Zero); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.GetPolicy(p => p.CircuitState == CircuitState.Open).Should().BeNull(); } @@ -251,7 +251,7 @@ public void GetPolicyTPolicy_with_predicate_should_throw_if_multiple_TPolicy_if_ Policy policyA = Policy.NoOp(); Policy policyB = Policy.Handle().Retry(); Policy policyC = Policy.NoOp(); - var wrap = policyA.Wrap(policyB.Wrap(policyC)); + PolicyWrap wrap = policyA.Wrap(policyB.Wrap(policyC)); wrap.Invoking(p => p.GetPolicy(_ => true)).Should().Throw(); } @@ -261,7 +261,7 @@ public void GetPolicyTPolicy_with_predicate_should_throw_if_predicate_is_null() { Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); + PolicyWrap wrap = policyA.Wrap(policyB); Action configure = () => wrap.GetPolicy(null); diff --git a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs index 8f9315855b9..c06672241da 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecs.cs @@ -13,9 +13,9 @@ public class PolicyWrapContextAndKeySpecs [Fact] public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onRetry = (_, _, context) => @@ -37,9 +37,9 @@ public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_Policy [Fact] public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onBreak = (_, _, context) => @@ -89,11 +89,11 @@ public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_mov [Fact] public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onBreak = (_, _, context) => @@ -121,11 +121,11 @@ public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_P [Fact] public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onBreak = (_, _, context) => @@ -141,7 +141,7 @@ public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Pol var innerWrap = retry.Wrap(breaker).WithPolicyKey(innerWrapKey); var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey); - var doneOnceOnly = false; + bool doneOnceOnly = false; outerWrap.Execute(() => { if (!doneOnceOnly) @@ -170,9 +170,9 @@ public class PolicyWrapTResultContextAndKeySpecs [Fact] public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action, int, Context> onRetry = (_, _, context) => @@ -194,9 +194,9 @@ public void Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_Policy [Fact] public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action, TimeSpan, Context> onBreak = (_, _, context) => @@ -237,7 +237,7 @@ public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_mov }) .WithPolicyKey("RetryPolicy"); - var policyWrap = Policy.Wrap(fallback, retry) + Policy policyWrap = Policy.Wrap(fallback, retry) .WithPolicyKey("PolicyWrap"); policyWrap.Execute(() => throw new Exception()); @@ -246,11 +246,11 @@ public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_mov [Fact] public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action, TimeSpan, Context> onBreak = (_, _, context) => diff --git a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs index dc031e31a06..24254002f2f 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs @@ -15,9 +15,9 @@ public class PolicyWrapContextAndKeySpecsAsync [Fact] public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onRetry = (_, _, context) => @@ -39,9 +39,9 @@ public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_ [Fact] public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onBreak = (_, _, context) => @@ -120,11 +120,11 @@ public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_ [Fact] public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onBreak = (_, _, context) => @@ -152,11 +152,11 @@ public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_i [Fact] public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Policy_when_execute_method_generic() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action onBreak = (_, _, context) => @@ -172,7 +172,7 @@ public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermo var innerWrap = retry.WrapAsync(breaker).WithPolicyKey(innerWrapKey); var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey); - var doneOnceOnly = false; + bool doneOnceOnly = false; await outerWrap.ExecuteAsync(() => { if (!doneOnceOnly) @@ -202,9 +202,9 @@ public class PolicyWrapTResultContextAndKeySpecsAsync [Fact] public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action, int, Context> onRetry = (_, _, context) => @@ -226,9 +226,9 @@ public async Task Should_pass_PolicyKey_to_execution_context_of_outer_policy_as_ [Fact] public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_PolicyWrapKey() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var wrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string wrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action, TimeSpan, Context> onBreak = (_, _, context) => @@ -314,11 +314,11 @@ public async Task Should_restore_PolicyKey_of_outer_policy_to_execution_context_ [Fact] public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() { - var retryKey = Guid.NewGuid().ToString(); - var breakerKey = Guid.NewGuid().ToString(); - var fallbackKey = Guid.NewGuid().ToString(); - var innerWrapKey = Guid.NewGuid().ToString(); - var outerWrapKey = Guid.NewGuid().ToString(); + string retryKey = Guid.NewGuid().ToString(); + string breakerKey = Guid.NewGuid().ToString(); + string fallbackKey = Guid.NewGuid().ToString(); + string innerWrapKey = Guid.NewGuid().ToString(); + string outerWrapKey = Guid.NewGuid().ToString(); string policyWrapKeySetOnExecutionContext = null; Action, TimeSpan, Context> onBreak = (_, _, context) => diff --git a/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs b/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs index 8143b5d62fc..1b0eca7b5b6 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapSpecs.cs @@ -16,7 +16,7 @@ public class PolicyWrapSpecs [Fact] public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() { - var retry = Policy.Handle().Retry(1); + RetryPolicy retry = Policy.Handle().Retry(1); Action config = () => retry.Wrap((Policy)null); @@ -26,7 +26,7 @@ public void Nongeneric_wraps_nongeneric_instance_syntax_wrapping_null_should_thr [Fact] public void Nongeneric_wraps_generic_instance_syntax_wrapping_null_should_throw() { - var retry = Policy.Handle().Retry(1); + RetryPolicy retry = Policy.Handle().Retry(1); Action config = () => retry.Wrap((Policy)null); @@ -39,7 +39,7 @@ public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_ou Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); + PolicyWrap wrap = policyA.Wrap(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -51,7 +51,7 @@ public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); + PolicyWrap wrap = policyA.Wrap(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -64,7 +64,7 @@ public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer [Fact] public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw() { - var retry = Policy.HandleResult(0).Retry(1); + RetryPolicy retry = Policy.HandleResult(0).Retry(1); Action config = () => retry.Wrap((Policy)null); @@ -75,7 +75,7 @@ public void Generic_wraps_nongeneric_instance_syntax_wrapping_null_should_throw( [Fact] public void Generic_wraps_generic_instance_syntax_wrapping_null_should_throw() { - var retry = Policy.HandleResult(0).Retry(1); + RetryPolicy retry = Policy.HandleResult(0).Retry(1); Action config = () => retry.Wrap((Policy)null); @@ -88,7 +88,7 @@ public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); + PolicyWrap wrap = policyA.Wrap(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -100,7 +100,7 @@ public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_in Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = policyA.Wrap(policyB); + PolicyWrap wrap = policyA.Wrap(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -295,7 +295,7 @@ public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = Policy.Wrap(policyA, policyB); + PolicyWrap wrap = Policy.Wrap(policyA, policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -350,7 +350,7 @@ public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set Policy policyA = Policy.NoOp(); Policy policyB = Policy.NoOp(); - var wrap = Policy.Wrap(policyA, policyB); + PolicyWrap wrap = Policy.Wrap(policyA, policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -363,11 +363,11 @@ public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set [Fact] public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() { - var retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); + RetryPolicy retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); - var retryWrappingBreaker = retry.Wrap(breaker); - var breakerWrappingRetry = breaker.Wrap(retry); + PolicyWrap retryWrappingBreaker = retry.Wrap(breaker); + PolicyWrap breakerWrappingRetry = breaker.Wrap(retry); // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. breaker.Reset(); @@ -385,8 +385,8 @@ public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_o [Fact] public void Wrapping_two_generic_policies_by_instance_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() { - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); + RetryPolicy retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); var retryWrappingBreaker = retry.Wrap(breaker); var breakerWrappingRetry = breaker.Wrap(retry); @@ -411,11 +411,11 @@ public void Wrapping_two_generic_policies_by_instance_syntax_and_executing_shoul [Fact] public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() { - var retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); + RetryPolicy retry = Policy.Handle().Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.Handle().CircuitBreaker(2, TimeSpan.MaxValue); - var retryWrappingBreaker = Policy.Wrap(retry, breaker); - var breakerWrappingRetry = Policy.Wrap(breaker, retry); + PolicyWrap retryWrappingBreaker = Policy.Wrap(retry, breaker); + PolicyWrap breakerWrappingRetry = Policy.Wrap(breaker, retry); // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. breaker.Reset(); @@ -433,8 +433,8 @@ public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_out [Fact] public void Wrapping_two_generic_policies_by_static_syntax_and_executing_should_wrap_outer_then_inner_around_delegate() { - var retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. - var breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); + RetryPolicy retry = Policy.HandleResult(ResultPrimitive.Fault).Retry(1); // Two tries in total: first try, plus one retry. + CircuitBreakerPolicy breaker = Policy.HandleResult(ResultPrimitive.Fault).CircuitBreaker(2, TimeSpan.MaxValue); var retryWrappingBreaker = Policy.Wrap(retry, breaker); var breakerWrappingRetry = Policy.Wrap(breaker, retry); @@ -459,15 +459,15 @@ public void Wrapping_two_generic_policies_by_static_syntax_and_executing_should_ [Fact] public void Outermost_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() { - var innerHandlingDBZE = Policy + CircuitBreakerPolicy innerHandlingDBZE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy + CircuitBreakerPolicy outerHandlingANE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); @@ -477,15 +477,15 @@ public void Outermost_policy_handling_exception_should_report_as_PolicyWrap_hand [Fact] public void Outermost_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() { - var innerHandlingDBZE = Policy + CircuitBreakerPolicy innerHandlingDBZE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy + CircuitBreakerPolicy outerHandlingANE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); @@ -495,15 +495,15 @@ public void Outermost_policy_not_handling_exception_even_if_inner_policies_do_sh [Fact] public void Outermost_generic_policy_handling_exception_should_report_as_PolicyWrap_handled_exception() { - var innerHandlingDBZE = Policy + CircuitBreakerPolicy innerHandlingDBZE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy + CircuitBreakerPolicy outerHandlingANE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new ArgumentNullException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); @@ -514,15 +514,15 @@ public void Outermost_generic_policy_handling_exception_should_report_as_PolicyW [Fact] public void Outermost_generic_policy_not_handling_exception_even_if_inner_policies_do_should_report_as_unhandled_exception() { - var innerHandlingDBZE = Policy + CircuitBreakerPolicy innerHandlingDBZE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingANE = Policy + CircuitBreakerPolicy outerHandlingANE = Policy .Handle() .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingANE.Wrap(innerHandlingDBZE); + PolicyWrap wrap = outerHandlingANE.Wrap(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => { throw new DivideByZeroException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); @@ -533,15 +533,15 @@ public void Outermost_generic_policy_not_handling_exception_even_if_inner_polici [Fact] public void Outermost_generic_policy_handling_result_should_report_as_PolicyWrap_handled_result() { - var innerHandlingFaultAgain = Policy + CircuitBreakerPolicy innerHandlingFaultAgain = Policy .HandleResult(ResultPrimitive.FaultAgain) .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingFault = Policy + CircuitBreakerPolicy outerHandlingFault = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); + PolicyWrap wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.Fault); + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.Fault); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); @@ -553,15 +553,15 @@ public void Outermost_generic_policy_handling_result_should_report_as_PolicyWrap [Fact] public void Outermost_generic_policy_not_handling_result_even_if_inner_policies_do_should_not_report_as_handled() { - var innerHandlingFaultAgain = Policy + CircuitBreakerPolicy innerHandlingFaultAgain = Policy .HandleResult(ResultPrimitive.FaultAgain) .CircuitBreaker(1, TimeSpan.Zero); - var outerHandlingFault = Policy + CircuitBreakerPolicy outerHandlingFault = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreaker(1, TimeSpan.Zero); - var wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); + PolicyWrap wrap = outerHandlingFault.Wrap(innerHandlingFaultAgain); - var executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.FaultAgain); + PolicyResult executeAndCaptureResultOnPolicyWrap = wrap.ExecuteAndCapture(() => ResultPrimitive.FaultAgain); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); diff --git a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs index 8ebcc74827d..5b0f9e85e91 100644 --- a/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs +++ b/src/Polly.Specs/Wrap/PolicyWrapSpecsAsync.cs @@ -39,7 +39,7 @@ public void Nongeneric_wraps_nongeneric_using_instance_wrap_syntax_should_set_ou AsyncPolicy policyA = Policy.NoOpAsync(); AsyncPolicy policyB = Policy.NoOpAsync(); - var wrap = policyA.WrapAsync(policyB); + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -51,7 +51,7 @@ public void Nongeneric_wraps_generic_using_instance_wrap_syntax_should_set_outer AsyncPolicy policyA = Policy.NoOpAsync(); AsyncPolicy policyB = Policy.NoOpAsync(); - var wrap = policyA.WrapAsync(policyB); + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -87,7 +87,7 @@ public void Generic_wraps_nongeneric_using_instance_wrap_syntax_should_set_outer AsyncPolicy policyA = Policy.NoOpAsync(); AsyncPolicy policyB = Policy.NoOpAsync(); - var wrap = policyA.WrapAsync(policyB); + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -99,7 +99,7 @@ public void Generic_wraps_generic_using_instance_wrap_syntax_should_set_outer_in AsyncPolicy policyA = Policy.NoOpAsync(); AsyncPolicy policyB = Policy.NoOpAsync(); - var wrap = policyA.WrapAsync(policyB); + AsyncPolicyWrap wrap = policyA.WrapAsync(policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -294,7 +294,7 @@ public void Wrapping_policies_using_static_wrap_syntax_should_set_outer_inner() AsyncPolicy policyA = Policy.NoOpAsync(); AsyncPolicy policyB = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(policyA, policyB); + AsyncPolicyWrap wrap = Policy.WrapAsync(policyA, policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -349,7 +349,7 @@ public void Wrapping_policies_using_static_wrap_strongly_typed_syntax_should_set AsyncPolicy policyA = Policy.NoOpAsync(); AsyncPolicy policyB = Policy.NoOpAsync(); - var wrap = Policy.WrapAsync(policyA, policyB); + AsyncPolicyWrap wrap = Policy.WrapAsync(policyA, policyB); wrap.Outer.Should().BeSameAs(policyA); wrap.Inner.Should().BeSameAs(policyB); @@ -365,8 +365,8 @@ public void Wrapping_two_policies_by_instance_syntax_and_executing_should_wrap_o var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); - var retryWrappingBreaker = retry.WrapAsync(breaker); - var breakerWrappingRetry = breaker.WrapAsync(retry); + AsyncPolicyWrap retryWrappingBreaker = retry.WrapAsync(breaker); + AsyncPolicyWrap breakerWrappingRetry = breaker.WrapAsync(retry); // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. breaker.Reset(); @@ -413,8 +413,8 @@ public void Wrapping_two_policies_by_static_syntax_and_executing_should_wrap_out var retry = Policy.Handle().RetryAsync(1); // Two tries in total: first try, plus one retry. var breaker = Policy.Handle().CircuitBreakerAsync(2, TimeSpan.MaxValue); - var retryWrappingBreaker = Policy.WrapAsync(retry, breaker); - var breakerWrappingRetry = Policy.WrapAsync(breaker, retry); + AsyncPolicyWrap retryWrappingBreaker = Policy.WrapAsync(retry, breaker); + AsyncPolicyWrap breakerWrappingRetry = Policy.WrapAsync(breaker, retry); // When the retry wraps the breaker, the retry (being outer) should cause the call to be put through the breaker twice - causing the breaker to break. breaker.Reset(); @@ -464,9 +464,9 @@ public async Task Outermost_policy_handling_exception_should_report_as_PolicyWra var outerHandlingANE = Policy .Handle() .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); @@ -483,9 +483,9 @@ public async Task Outermost_policy_not_handling_exception_even_if_inner_policies var outerHandlingANE = Policy .Handle() .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); @@ -502,9 +502,9 @@ public async Task Outermost_generic_policy_handling_exception_should_report_as_P var outerHandlingANE = Policy .Handle() .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new ArgumentNullException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); @@ -521,9 +521,9 @@ public async Task Outermost_generic_policy_not_handling_exception_even_if_inner_ var outerHandlingANE = Policy .Handle() .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); + AsyncPolicyWrap wrap = outerHandlingANE.WrapAsync(innerHandlingDBZE); - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => { throw new DivideByZeroException(); }); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FinalException.Should().BeOfType(); @@ -540,9 +540,9 @@ public async Task Outermost_generic_policy_handling_result_should_report_as_Poli var outerHandlingFault = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); + AsyncPolicyWrap wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.Fault)); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Failure); executeAndCaptureResultOnPolicyWrap.FaultType.Should().Be(FaultType.ResultHandledByThisPolicy); @@ -560,9 +560,9 @@ public async Task Outermost_generic_policy_not_handling_result_even_if_inner_pol var outerHandlingFault = Policy .HandleResult(ResultPrimitive.Fault) .CircuitBreakerAsync(1, TimeSpan.Zero); - var wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); + AsyncPolicyWrap wrap = outerHandlingFault.WrapAsync(innerHandlingFaultAgain); - var executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); + PolicyResult executeAndCaptureResultOnPolicyWrap = await wrap.ExecuteAndCaptureAsync(() => Task.FromResult(ResultPrimitive.FaultAgain)); executeAndCaptureResultOnPolicyWrap.Outcome.Should().Be(OutcomeType.Successful); executeAndCaptureResultOnPolicyWrap.FinalHandledResult.Should().Be(default(ResultPrimitive)); diff --git a/src/Polly/AsyncPolicy.ExecuteOverloads.cs b/src/Polly/AsyncPolicy.ExecuteOverloads.cs index ef70f37f199..3a43863ab0e 100644 --- a/src/Polly/AsyncPolicy.ExecuteOverloads.cs +++ b/src/Polly/AsyncPolicy.ExecuteOverloads.cs @@ -101,7 +101,7 @@ public async Task ExecuteAsync(Func action, Co { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); try { @@ -221,7 +221,7 @@ public async Task ExecuteAsync(Func ExecuteAsync(Func> ExecuteAndCaptureAsync(Func ImplementationAsync( { cancellationToken.ThrowIfCancellationRequested(); - var cacheKey = cacheKeyStrategy(context); + string cacheKey = cacheKeyStrategy(context); if (cacheKey == null) { return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); @@ -50,9 +50,9 @@ internal static async Task ImplementationAsync( onCacheMiss(context, cacheKey); } - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - var ttl = ttlStrategy.GetTtl(context, result); + Ttl ttl = ttlStrategy.GetTtl(context, result); if (ttl.Timespan > TimeSpan.Zero) { try diff --git a/src/Polly/Caching/AsyncGenericCacheProvider.cs b/src/Polly/Caching/AsyncGenericCacheProvider.cs index 5d4be99a188..a0a476400de 100644 --- a/src/Polly/Caching/AsyncGenericCacheProvider.cs +++ b/src/Polly/Caching/AsyncGenericCacheProvider.cs @@ -17,7 +17,7 @@ internal AsyncGenericCacheProvider(IAsyncCacheProvider nonGenericCacheProvider) async Task<(bool, TCacheFormat)> IAsyncCacheProvider.TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - (var cacheHit, var result) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + (bool cacheHit, object result) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); return (cacheHit, (TCacheFormat)(result ?? default(TCacheFormat))); } diff --git a/src/Polly/Caching/AsyncSerializingCacheProvider.cs b/src/Polly/Caching/AsyncSerializingCacheProvider.cs index 86f2d4ae6a8..c4c8a17a0ea 100644 --- a/src/Polly/Caching/AsyncSerializingCacheProvider.cs +++ b/src/Polly/Caching/AsyncSerializingCacheProvider.cs @@ -38,7 +38,7 @@ public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCac /// public async Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - (var cacheHit, var objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + (bool cacheHit, TSerialized objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); } @@ -99,7 +99,7 @@ public AsyncSerializingCacheProvider(IAsyncCacheProvider wrappedCac /// public async Task<(bool, TResult)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - (var cacheHit, var objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + (bool cacheHit, TSerialized objectToDeserialize) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); } diff --git a/src/Polly/Caching/CacheEngine.cs b/src/Polly/Caching/CacheEngine.cs index a1c66b18551..192841e7eb5 100644 --- a/src/Polly/Caching/CacheEngine.cs +++ b/src/Polly/Caching/CacheEngine.cs @@ -20,7 +20,7 @@ internal static TResult Implementation( { cancellationToken.ThrowIfCancellationRequested(); - var cacheKey = cacheKeyStrategy(context); + string cacheKey = cacheKeyStrategy(context); if (cacheKey == null) { return action(context, cancellationToken); @@ -48,9 +48,9 @@ internal static TResult Implementation( onCacheMiss(context, cacheKey); } - var result = action(context, cancellationToken); + TResult result = action(context, cancellationToken); - var ttl = ttlStrategy.GetTtl(context, result); + Ttl ttl = ttlStrategy.GetTtl(context, result); if (ttl.Timespan > TimeSpan.Zero) { try diff --git a/src/Polly/Caching/ContextualTtl.cs b/src/Polly/Caching/ContextualTtl.cs index d9844d94e4b..186e0062053 100644 --- a/src/Polly/Caching/ContextualTtl.cs +++ b/src/Polly/Caching/ContextualTtl.cs @@ -30,9 +30,9 @@ public Ttl GetTtl(Context context, object result) { if (!context.ContainsKey(TimeSpanKey)) return _noTtl; - var sliding = false; + bool sliding = false; - if (context.TryGetValue(SlidingExpirationKey, out var objValue)) + if (context.TryGetValue(SlidingExpirationKey, out object objValue)) { sliding = objValue as bool? ?? false; } diff --git a/src/Polly/Caching/GenericCacheProvider.cs b/src/Polly/Caching/GenericCacheProvider.cs index 762323f8ffb..122cf09efdb 100644 --- a/src/Polly/Caching/GenericCacheProvider.cs +++ b/src/Polly/Caching/GenericCacheProvider.cs @@ -15,7 +15,7 @@ internal GenericCacheProvider(ISyncCacheProvider nonGenericCacheProvider) (bool, TCacheFormat) ISyncCacheProvider.TryGet(string key) { - (var cacheHit, var result) = _wrappedCacheProvider.TryGet(key); + (bool cacheHit, object result) = _wrappedCacheProvider.TryGet(key); return (cacheHit, (TCacheFormat) (result ?? default(TCacheFormat))); } diff --git a/src/Polly/Caching/NonSlidingTtl.cs b/src/Polly/Caching/NonSlidingTtl.cs index 91f48e19221..e366524d880 100644 --- a/src/Polly/Caching/NonSlidingTtl.cs +++ b/src/Polly/Caching/NonSlidingTtl.cs @@ -28,8 +28,8 @@ protected NonSlidingTtl(DateTimeOffset absoluteExpirationTime) /// A representing the remaining Ttl of the cached item. public Ttl GetTtl(Context context, object result) { - var untilPointInTime = absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow()); - var remaining = untilPointInTime > TimeSpan.Zero ? untilPointInTime : TimeSpan.Zero; + TimeSpan untilPointInTime = absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow()); + TimeSpan remaining = untilPointInTime > TimeSpan.Zero ? untilPointInTime : TimeSpan.Zero; return new Ttl(remaining, false); } } diff --git a/src/Polly/Caching/SerializingCacheProvider.cs b/src/Polly/Caching/SerializingCacheProvider.cs index 6a32e84ed34..9d57a941b29 100644 --- a/src/Polly/Caching/SerializingCacheProvider.cs +++ b/src/Polly/Caching/SerializingCacheProvider.cs @@ -34,7 +34,7 @@ public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProv /// public (bool, object) TryGet(string key) { - (var cacheHit, var objectToDeserialize) = _wrappedCacheProvider.TryGet(key); + (bool cacheHit, TSerialized objectToDeserialize) = _wrappedCacheProvider.TryGet(key); return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : null); } @@ -84,7 +84,7 @@ public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProv /// public (bool, TResult) TryGet(string key) { - (var cacheHit, var objectToDeserialize) = _wrappedCacheProvider.TryGet(key); + (bool cacheHit, TSerialized objectToDeserialize) = _wrappedCacheProvider.TryGet(key); return (cacheHit, cacheHit ? _serializer.Deserialize(objectToDeserialize) : default); } diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs index 8ac0ddb82a3..8e707fd7272 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerSyntax.cs @@ -35,7 +35,7 @@ public static class AdvancedCircuitBreakerSyntax public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.AdvancedCircuitBreaker( failureThreshold, samplingDuration, minimumThroughput, @@ -107,7 +107,7 @@ public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder pol /// onReset public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, durationOfBreak, onBreak, diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs index d04c90f103b..b06eb42bfc8 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitBreakerTResultSyntax.cs @@ -35,7 +35,7 @@ public static class AdvancedCircuitBreakerTResultSyntax public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.AdvancedCircuitBreaker( failureThreshold, samplingDuration, minimumThroughput, @@ -107,7 +107,7 @@ public static CircuitBreakerPolicy AdvancedCircuitBreaker(this /// onReset public static CircuitBreakerPolicy AdvancedCircuitBreaker(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.AdvancedCircuitBreaker(failureThreshold, samplingDuration, minimumThroughput, durationOfBreak, onBreak, diff --git a/src/Polly/CircuitBreaker/AdvancedCircuitController.cs b/src/Polly/CircuitBreaker/AdvancedCircuitController.cs index aa0d5cae56a..8596d4f6b62 100644 --- a/src/Polly/CircuitBreaker/AdvancedCircuitController.cs +++ b/src/Polly/CircuitBreaker/AdvancedCircuitController.cs @@ -84,7 +84,7 @@ public override void OnActionFailure(DelegateResult outcome, Context co _metrics.IncrementFailure_NeedsLock(); var healthCount = _metrics.GetHealthCount_NeedsLock(); - var throughput = healthCount.Total; + int throughput = healthCount.Total; if (throughput >= _minimumThroughput && ((double)healthCount.Failures) / throughput >= _failureThreshold) { Break_NeedsLock(context); diff --git a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs index f5b6336de4e..9bb666e577b 100644 --- a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerSyntax.cs @@ -36,7 +36,7 @@ public static class AsyncAdvancedCircuitBreakerSyntax public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.AdvancedCircuitBreakerAsync( failureThreshold, samplingDuration, minimumThroughput, @@ -110,7 +110,7 @@ public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyB /// onReset public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.AdvancedCircuitBreakerAsync( failureThreshold, samplingDuration, minimumThroughput, durationOfBreak, diff --git a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs index e339b9960d7..9f4f2ddf711 100644 --- a/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncAdvancedCircuitBreakerTResultSyntax.cs @@ -35,7 +35,7 @@ public static class AsyncAdvancedCircuitBreakerTResultSyntax public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak) { Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.AdvancedCircuitBreakerAsync( failureThreshold, samplingDuration, minimumThroughput, @@ -109,7 +109,7 @@ public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsynconReset public static AsyncCircuitBreakerPolicy AdvancedCircuitBreakerAsync(this PolicyBuilder policyBuilder, double failureThreshold, TimeSpan samplingDuration, int minimumThroughput, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.AdvancedCircuitBreakerAsync( failureThreshold, samplingDuration, minimumThroughput, durationOfBreak, diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs index 38c690c9a63..eadf0f0bd55 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs @@ -22,7 +22,7 @@ internal static async Task ImplementationAsync( try { - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); if (shouldHandleResultPredicates.AnyMatch(result)) { @@ -37,7 +37,7 @@ internal static async Task ImplementationAsync( } catch (Exception ex) { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs index fdeda3b388d..f111cff360f 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerSyntax.cs @@ -30,7 +30,7 @@ public static class AsyncCircuitBreakerSyntax public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) { Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.CircuitBreakerAsync( exceptionsAllowedBeforeBreaking, @@ -94,7 +94,7 @@ public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder p /// onReset public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.CircuitBreakerAsync( exceptionsAllowedBeforeBreaking, durationOfBreak, diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs index 08024da3f68..48b8d32344d 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerTResultSyntax.cs @@ -29,7 +29,7 @@ public static class AsyncCircuitBreakerTResultSyntax public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) { Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.CircuitBreakerAsync( handledEventsAllowedBeforeBreaking, @@ -93,7 +93,7 @@ public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(th /// onReset public static AsyncCircuitBreakerPolicy CircuitBreakerAsync(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.CircuitBreakerAsync( handledEventsAllowedBeforeBreaking, durationOfBreak, diff --git a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs index 1629da85111..141eb4167c7 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs @@ -20,7 +20,7 @@ internal static TResult Implementation( try { - var result = action(context, cancellationToken); + TResult result = action(context, cancellationToken); if (shouldHandleResultPredicates.AnyMatch(result)) { @@ -35,7 +35,7 @@ internal static TResult Implementation( } catch (Exception ex) { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; diff --git a/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs b/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs index a63665a6751..124e51bad62 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerSyntax.cs @@ -32,7 +32,7 @@ public static class CircuitBreakerSyntax public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak) { Action doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.CircuitBreaker (exceptionsAllowedBeforeBreaking, @@ -96,7 +96,7 @@ public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuild /// onReset public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.CircuitBreaker(exceptionsAllowedBeforeBreaking, durationOfBreak, onBreak, diff --git a/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs b/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs index 5c42d3fbf84..69e79cdee53 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerTResultSyntax.cs @@ -31,7 +31,7 @@ public static class CircuitBreakerTResultSyntax public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak) { Action, TimeSpan> doNothingOnBreak = (_, _) => { }; - var doNothingOnReset = () => { }; + Action doNothingOnReset = () => { }; return policyBuilder.CircuitBreaker (handledEventsAllowedBeforeBreaking, @@ -95,7 +95,7 @@ public static CircuitBreakerPolicy CircuitBreaker(this PolicyB /// onReset public static CircuitBreakerPolicy CircuitBreaker(this PolicyBuilder policyBuilder, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak, Action, TimeSpan, Context> onBreak, Action onReset) { - var doNothingOnHalfOpen = () => { }; + Action doNothingOnHalfOpen = () => { }; return policyBuilder.CircuitBreaker(handledEventsAllowedBeforeBreaking, durationOfBreak, onBreak, diff --git a/src/Polly/CircuitBreaker/CircuitStateController.cs b/src/Polly/CircuitBreaker/CircuitStateController.cs index e44737312f4..d6fdaaf07d0 100644 --- a/src/Polly/CircuitBreaker/CircuitStateController.cs +++ b/src/Polly/CircuitBreaker/CircuitStateController.cs @@ -98,7 +98,7 @@ public void Isolate() private void BreakFor_NeedsLock(TimeSpan durationOfBreak, Context context) { - var willDurationTakeUsPastDateTimeMaxValue = durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow(); + bool willDurationTakeUsPastDateTimeMaxValue = durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow(); _blockedTill = willDurationTakeUsPastDateTimeMaxValue ? DateTime.MaxValue.Ticks : (SystemClock.UtcNow() + durationOfBreak).Ticks; @@ -116,7 +116,7 @@ protected void ResetInternal_NeedsLock(Context context) _blockedTill = DateTime.MinValue.Ticks; _lastOutcome = null; - var priorState = _circuitState; + CircuitState priorState = _circuitState; _circuitState = CircuitState.Closed; if (priorState != CircuitState.Closed) { @@ -126,7 +126,7 @@ protected void ResetInternal_NeedsLock(Context context) protected bool PermitHalfOpenCircuitTest() { - var currentlyBlockedUntil = _blockedTill; + long currentlyBlockedUntil = _blockedTill; if (SystemClock.UtcNow().Ticks >= currentlyBlockedUntil) { // It's time to permit a / another trial call in the half-open state ... diff --git a/src/Polly/CircuitBreaker/RollingHealthMetrics.cs b/src/Polly/CircuitBreaker/RollingHealthMetrics.cs index 44e4fb25fc1..9b25b2d45fa 100644 --- a/src/Polly/CircuitBreaker/RollingHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/RollingHealthMetrics.cs @@ -44,8 +44,8 @@ public HealthCount GetHealthCount_NeedsLock() { ActualiseCurrentMetric_NeedsLock(); - var successes = 0; - var failures = 0; + int successes = 0; + int failures = 0; foreach (var window in _windows) { successes += window.Successes; @@ -62,7 +62,7 @@ public HealthCount GetHealthCount_NeedsLock() private void ActualiseCurrentMetric_NeedsLock() { - var now = SystemClock.UtcNow().Ticks; + long now = SystemClock.UtcNow().Ticks; if (_currentWindow == null || now - _currentWindow.StartedAt >= _windowDuration) { _currentWindow = new HealthCount { StartedAt = now }; diff --git a/src/Polly/CircuitBreaker/SingleHealthMetrics.cs b/src/Polly/CircuitBreaker/SingleHealthMetrics.cs index f56546720b6..4a604389620 100644 --- a/src/Polly/CircuitBreaker/SingleHealthMetrics.cs +++ b/src/Polly/CircuitBreaker/SingleHealthMetrics.cs @@ -36,7 +36,7 @@ public HealthCount GetHealthCount_NeedsLock() private void ActualiseCurrentMetric_NeedsLock() { - var now = SystemClock.UtcNow().Ticks; + long now = SystemClock.UtcNow().Ticks; if (_current == null || now - _current.StartedAt >= _samplingDuration) { _current = new HealthCount { StartedAt = now }; diff --git a/src/Polly/Fallback/AsyncFallbackEngine.cs b/src/Polly/Fallback/AsyncFallbackEngine.cs index 51997811bd4..c8a256d1976 100644 --- a/src/Polly/Fallback/AsyncFallbackEngine.cs +++ b/src/Polly/Fallback/AsyncFallbackEngine.cs @@ -22,7 +22,7 @@ internal static async Task ImplementationAsync( { cancellationToken.ThrowIfCancellationRequested(); - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); if (!shouldHandleResultPredicates.AnyMatch(result)) { @@ -33,7 +33,7 @@ internal static async Task ImplementationAsync( } catch (Exception ex) { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; diff --git a/src/Polly/Fallback/FallbackEngine.cs b/src/Polly/Fallback/FallbackEngine.cs index 65ebee78cc8..eae49ff82ca 100644 --- a/src/Polly/Fallback/FallbackEngine.cs +++ b/src/Polly/Fallback/FallbackEngine.cs @@ -20,7 +20,7 @@ internal static TResult Implementation( { cancellationToken.ThrowIfCancellationRequested(); - var result = action(context, cancellationToken); + TResult result = action(context, cancellationToken); if (!shouldHandleResultPredicates.AnyMatch(result)) { @@ -31,7 +31,7 @@ internal static TResult Implementation( } catch (Exception ex) { - var handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; diff --git a/src/Polly/Policy.ExecuteOverloads.cs b/src/Polly/Policy.ExecuteOverloads.cs index afbfb91ba0e..df6ae88239f 100644 --- a/src/Polly/Policy.ExecuteOverloads.cs +++ b/src/Polly/Policy.ExecuteOverloads.cs @@ -66,7 +66,7 @@ public void Execute(Action action, Context context, { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); try { @@ -157,7 +157,7 @@ public TResult Execute(Func action { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); try { diff --git a/src/Polly/Policy.TResult.ExecuteOverloads.cs b/src/Polly/Policy.TResult.ExecuteOverloads.cs index 10505854f9f..27475426a59 100644 --- a/src/Polly/Policy.TResult.ExecuteOverloads.cs +++ b/src/Polly/Policy.TResult.ExecuteOverloads.cs @@ -82,7 +82,7 @@ public TResult Execute(Func action, Context { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context, out var priorPolicyWrapKey, out var priorPolicyKey); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); try { @@ -165,7 +165,7 @@ public PolicyResult ExecuteAndCapture(Func ImplementationAsync( bool continueOnCapturedContext ) { - (var permit, var retryAfter) = rateLimiter.PermitExecution(); + (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution(); if (permit) { diff --git a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs index 62bb3408195..2e76dce61e9 100644 --- a/src/Polly/RateLimit/AsyncRateLimitSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitSyntax.cs @@ -42,7 +42,7 @@ public static AsyncRateLimitPolicy RateLimitAsync( throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); return new AsyncRateLimitPolicy(rateLimiter); } diff --git a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs index 64ba73141cf..1a07d805e29 100644 --- a/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs @@ -82,7 +82,7 @@ public static AsyncRateLimitPolicy RateLimitAsync( throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); return new AsyncRateLimitPolicy(rateLimiter, retryAfterFactory); } diff --git a/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs b/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs index e1e0ec468ac..b4b3dd54841 100644 --- a/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs +++ b/src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs @@ -47,7 +47,7 @@ public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity) while (true) { // Try to get a token. - var tokensAfterGrabOne = Interlocked.Decrement(ref currentTokens); + long tokensAfterGrabOne = Interlocked.Decrement(ref currentTokens); if (tokensAfterGrabOne >= 0) { @@ -56,9 +56,9 @@ public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity) } // No tokens! We're rate-limited - unless we can refill the bucket. - var now = SystemClock.DateTimeOffsetUtcNow().Ticks; - var currentAddNextTokenAtTicks = Interlocked.Read(ref addNextTokenAtTicks); - var ticksTillAddNextToken = currentAddNextTokenAtTicks - now; + long now = SystemClock.DateTimeOffsetUtcNow().Ticks; + long currentAddNextTokenAtTicks = Interlocked.Read(ref addNextTokenAtTicks); + long ticksTillAddNextToken = currentAddNextTokenAtTicks - now; if (ticksTillAddNextToken > 0) { @@ -69,17 +69,17 @@ public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity) // Time to add tokens to the bucket! // We definitely need to add one token. In fact, if we haven't hit this bit of code for a while, we might be due to add a bunch of tokens. - var tokensMissedAdding = + long tokensMissedAdding = // Passing addNextTokenAtTicks merits one token 1 + // And any whole token tick intervals further each merit another. (-ticksTillAddNextToken / addTokenTickInterval); // We mustn't exceed bucket capacity though. - var tokensToAdd = Math.Min(bucketCapacity, tokensMissedAdding); + long tokensToAdd = Math.Min(bucketCapacity, tokensMissedAdding); // Work out when tokens would next be due to be added, if we add these tokens. - var newAddNextTokenAtTicks = currentAddNextTokenAtTicks + (tokensToAdd * addTokenTickInterval); + long newAddNextTokenAtTicks = currentAddNextTokenAtTicks + (tokensToAdd * addTokenTickInterval); // But if we were way overdue refilling the bucket (there was inactivity for a while), that value would be out-of-date: the next time we add tokens must be at least addTokenTickInterval from now. newAddNextTokenAtTicks = Math.Max(newAddNextTokenAtTicks, now + addTokenTickInterval); diff --git a/src/Polly/RateLimit/RateLimitEngine.cs b/src/Polly/RateLimit/RateLimitEngine.cs index d94f9f9b3be..a96cbb2835d 100644 --- a/src/Polly/RateLimit/RateLimitEngine.cs +++ b/src/Polly/RateLimit/RateLimitEngine.cs @@ -13,7 +13,7 @@ internal static TResult Implementation( CancellationToken cancellationToken ) { - (var permit, var retryAfter) = rateLimiter.PermitExecution(); + (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution(); if (permit) { diff --git a/src/Polly/RateLimit/RateLimitSyntax.cs b/src/Polly/RateLimit/RateLimitSyntax.cs index f73095b9577..fbdd993aa0a 100644 --- a/src/Polly/RateLimit/RateLimitSyntax.cs +++ b/src/Polly/RateLimit/RateLimitSyntax.cs @@ -42,7 +42,7 @@ public static RateLimitPolicy RateLimit( throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); return new RateLimitPolicy(rateLimiter); } diff --git a/src/Polly/RateLimit/RateLimitTResultSyntax.cs b/src/Polly/RateLimit/RateLimitTResultSyntax.cs index 45b67cae351..17adaf53e7f 100644 --- a/src/Polly/RateLimit/RateLimitTResultSyntax.cs +++ b/src/Polly/RateLimit/RateLimitTResultSyntax.cs @@ -82,7 +82,7 @@ public static RateLimitPolicy RateLimit( throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive."); } - var rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); + IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst); return new RateLimitPolicy(rateLimiter, retryAfterFactory); } diff --git a/src/Polly/Registry/PolicyRegistry.cs b/src/Polly/Registry/PolicyRegistry.cs index ab76b4b8df3..a724d4cb835 100644 --- a/src/Polly/Registry/PolicyRegistry.cs +++ b/src/Polly/Registry/PolicyRegistry.cs @@ -112,7 +112,7 @@ public TPolicy Get(string key) where TPolicy : IsPolicy => /// True if Policy exists for the provided Key. False otherwise. public bool TryGet(string key, out TPolicy policy) where TPolicy : IsPolicy { - var got = _registry.TryGetValue(key, out var value); + bool got = _registry.TryGetValue(key, out IsPolicy value); policy = got ? (TPolicy)value : default; return got; } @@ -156,7 +156,7 @@ public bool TryRemove(string key, out TPolicy policy) where TPolicy : I { var registry = ThrowIfNotConcurrentImplementation(); - var got = registry.TryRemove(key, out var value); + bool got = registry.TryRemove(key, out IsPolicy value); policy = got ? (TPolicy) value : default; return got; } diff --git a/src/Polly/Retry/AsyncRetryEngine.cs b/src/Polly/Retry/AsyncRetryEngine.cs index 0188d983bdc..56f167a2445 100644 --- a/src/Polly/Retry/AsyncRetryEngine.cs +++ b/src/Polly/Retry/AsyncRetryEngine.cs @@ -20,8 +20,8 @@ internal static async Task ImplementationAsync( Func, Context, TimeSpan> sleepDurationProvider = null, bool continueOnCapturedContext = false) { - var tryCount = 0; - var sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); + int tryCount = 0; + IEnumerator sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); try { @@ -34,7 +34,7 @@ internal static async Task ImplementationAsync( try { - var result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); + TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); if (!shouldRetryResultPredicates.AnyMatch(result)) { @@ -52,7 +52,7 @@ internal static async Task ImplementationAsync( } catch (Exception ex) { - var handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; @@ -71,7 +71,7 @@ internal static async Task ImplementationAsync( if (tryCount < int.MaxValue) { tryCount++; } - var waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); + TimeSpan waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); await onRetryAsync(outcome, waitDuration, tryCount, context).ConfigureAwait(continueOnCapturedContext); diff --git a/src/Polly/Retry/AsyncRetrySyntax.cs b/src/Polly/Retry/AsyncRetrySyntax.cs index 9443c0e49bf..8b9449a4aac 100644 --- a/src/Polly/Retry/AsyncRetrySyntax.cs +++ b/src/Polly/Retry/AsyncRetrySyntax.cs @@ -505,7 +505,7 @@ public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilde if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - var sleepDurations = Enumerable.Range(1, retryCount) + IEnumerable sleepDurations = Enumerable.Range(1, retryCount) .Select(sleepDurationProvider); return new AsyncRetryPolicy( diff --git a/src/Polly/Retry/AsyncRetryTResultSyntax.cs b/src/Polly/Retry/AsyncRetryTResultSyntax.cs index 6d7cc6993a1..8dc131281c0 100644 --- a/src/Polly/Retry/AsyncRetryTResultSyntax.cs +++ b/src/Polly/Retry/AsyncRetryTResultSyntax.cs @@ -505,7 +505,7 @@ public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBu if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - var sleepDurations = Enumerable.Range(1, retryCount) + IEnumerable sleepDurations = Enumerable.Range(1, retryCount) .Select(sleepDurationProvider); return new AsyncRetryPolicy( diff --git a/src/Polly/Retry/RetryEngine.cs b/src/Polly/Retry/RetryEngine.cs index 5679a12a1bd..f61a2e396c7 100644 --- a/src/Polly/Retry/RetryEngine.cs +++ b/src/Polly/Retry/RetryEngine.cs @@ -18,8 +18,8 @@ internal static TResult Implementation( IEnumerable sleepDurationsEnumerable = null, Func, Context, TimeSpan> sleepDurationProvider = null) { - var tryCount = 0; - var sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); + int tryCount = 0; + IEnumerator sleepDurationsEnumerator = sleepDurationsEnumerable?.GetEnumerator(); try { @@ -32,7 +32,7 @@ internal static TResult Implementation( try { - var result = action(context, cancellationToken); + TResult result = action(context, cancellationToken); if (!shouldRetryResultPredicates.AnyMatch(result)) { @@ -50,7 +50,7 @@ internal static TResult Implementation( } catch (Exception ex) { - var handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); + Exception handledException = shouldRetryExceptionPredicates.FirstMatchOrDefault(ex); if (handledException == null) { throw; @@ -69,7 +69,7 @@ internal static TResult Implementation( if (tryCount < int.MaxValue) { tryCount++; } - var waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); + TimeSpan waitDuration = sleepDurationsEnumerator?.Current ?? (sleepDurationProvider?.Invoke(tryCount, outcome, context) ?? TimeSpan.Zero); onRetry(outcome, waitDuration, tryCount, context); diff --git a/src/Polly/Timeout/AsyncTimeoutEngine.cs b/src/Polly/Timeout/AsyncTimeoutEngine.cs index 1f492cb47a4..fff1ebecc5f 100644 --- a/src/Polly/Timeout/AsyncTimeoutEngine.cs +++ b/src/Polly/Timeout/AsyncTimeoutEngine.cs @@ -17,14 +17,14 @@ internal static async Task ImplementationAsync( bool continueOnCapturedContext) { cancellationToken.ThrowIfCancellationRequested(); - var timeout = timeoutProvider(context); + TimeSpan timeout = timeoutProvider(context); - using (var timeoutCancellationTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource()) { - using (var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) + using (CancellationTokenSource combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { Task actionTask = null; - var combinedToken = combinedTokenSource.Token; + CancellationToken combinedToken = combinedTokenSource.Token; try { @@ -36,7 +36,7 @@ internal static async Task ImplementationAsync( // else: timeoutStrategy == TimeoutStrategy.Pessimistic - var timeoutTask = timeoutCancellationTokenSource.Token.AsTask(); + Task timeoutTask = timeoutCancellationTokenSource.Token.AsTask(); SystemClock.CancelTokenAfter(timeoutCancellationTokenSource, timeout); diff --git a/src/Polly/Timeout/TimeoutEngine.cs b/src/Polly/Timeout/TimeoutEngine.cs index e236c22e6b6..ae6c8bb81b6 100644 --- a/src/Polly/Timeout/TimeoutEngine.cs +++ b/src/Polly/Timeout/TimeoutEngine.cs @@ -17,13 +17,13 @@ internal static TResult Implementation( Action onTimeout) { cancellationToken.ThrowIfCancellationRequested(); - var timeout = timeoutProvider(context); + TimeSpan timeout = timeoutProvider(context); - using (var timeoutCancellationTokenSource = new CancellationTokenSource()) + using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource()) { - using (var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) + using (CancellationTokenSource combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token)) { - var combinedToken = combinedTokenSource.Token; + CancellationToken combinedToken = combinedTokenSource.Token; Task actionTask = null; try diff --git a/src/Polly/Utilities/TimedLock.cs b/src/Polly/Utilities/TimedLock.cs index d889979ee20..6c76efe7c18 100644 --- a/src/Polly/Utilities/TimedLock.cs +++ b/src/Polly/Utilities/TimedLock.cs @@ -32,7 +32,7 @@ public static TimedLock Lock(object o) private static TimedLock Lock(object o, TimeSpan timeout) { - var tl = new TimedLock(o); + TimedLock tl = new TimedLock(o); if (!Monitor.TryEnter(o, timeout)) { #if DEBUG From 35cf357c09c0c48804165ac079f1c04ce67d12f6 Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 14:55:39 +0000 Subject: [PATCH 21/24] Update .git-blame-ignore-revs Add revert commit. --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index c25b162ab40..8c69262d34c 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,3 +1,4 @@ # Use file-scoped namespaces a450cb69b5e4549f5515cdb057a68771f56cefd7 e32730600746b479db0241826b9bdef8eec9bb28 +63b151ebc191ef35df9e3f29584ca94ec5d7c792 From b08876bdf74c3640af725fce95d2e3a57e789139 Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 15:15:29 +0000 Subject: [PATCH 22/24] Add GitHub Actions CI Add a GitHub Actions CI to see if it's more stable than AppVeyor's flakiness. --- .github/workflows/build.yml | 44 +++++++++++++++++++++++++++++++++++++ global.json | 4 +++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..a4835e89023 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,44 @@ +name: build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ windows-latest ] + + steps: + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v3 + + - name: Build, Test and Package + shell: pwsh + run: ./build.ps1 + env: + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + DOTNET_NOLOGO: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: 1 + NUGET_XMLDOC_MODE: skip + TERM: xterm + + - name: Publish NuGet packages + uses: actions/upload-artifact@v3 + with: + name: packages-${{ matrix.os_name }} + path: ./artifacts/nuget-package diff --git a/global.json b/global.json index 47a7fa60368..124ed58f386 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,7 @@ { "sdk": { - "allowPrerelease": false + "version": "6.0.404", + "allowPrerelease": false, + "rollForward": "latestMajor" } } From 6f4d997f213d2343b445510c98cc0fab1f46f453 Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 15:24:24 +0000 Subject: [PATCH 23/24] Add build matrix - Add Linux and macOS to the mix. - Add include which should fix the artifact upload name. --- .github/workflows/build.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4835e89023..c4bedf63740 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,14 @@ jobs: strategy: fail-fast: false matrix: - os: [ windows-latest ] + os: [ macos-latest, ubuntu-latest, windows-latest ] + include: + - os: macos-latest + os_name: macos + - os: ubuntu-latest + os_name: linux + - os: windows-latest + os_name: windows steps: From 1073e821a2c2a4e66e18f4c8db27c9b8a880f863 Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 16 Dec 2022 15:31:00 +0000 Subject: [PATCH 24/24] Remove macOS and Linux Remove them for now as they don't work. --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c4bedf63740..6caebf98f56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,12 +15,12 @@ jobs: strategy: fail-fast: false matrix: - os: [ macos-latest, ubuntu-latest, windows-latest ] + os: [ windows-latest ] include: - - os: macos-latest - os_name: macos - - os: ubuntu-latest - os_name: linux + #- os: macos-latest + # os_name: macos + #- os: ubuntu-latest + # os_name: linux - os: windows-latest os_name: windows